package libraryelements import ( "context" "encoding/json" "errors" "fmt" "strings" "time" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) const ( selectLibraryElementDTOWithMeta = ` SELECT DISTINCT le.name, le.id, le.org_id, le.folder_id, le.uid, le.kind, le.type, le.description, le.model, le.created, le.created_by, le.updated, le.updated_by, le.version , u1.login AS created_by_name , u1.email AS created_by_email , u2.login AS updated_by_name , u2.email AS updated_by_email , (SELECT COUNT(connection_id) FROM ` + models.LibraryElementConnectionTableName + ` WHERE element_id = le.id AND kind=1) AS connected_dashboards` ) // redundant SELECT to trick mysql's optimizer const deleteInvalidConnections = ` DELETE FROM library_element_connection WHERE connection_id IN ( SELECT connection_id FROM ( SELECT connection_id as id FROM library_element_connection WHERE element_id=? AND connection_id NOT IN (SELECT id as connection_id from dashboard) ) as dummy )` func getFromLibraryElementDTOWithMeta(dialect migrator.Dialect) string { user := dialect.Quote("user") userJoin := ` FROM library_element AS le LEFT JOIN ` + user + ` AS u1 ON le.created_by = u1.id LEFT JOIN ` + user + ` AS u2 ON le.updated_by = u2.id ` return userJoin } func syncFieldsWithModel(libraryElement *LibraryElement) error { var model map[string]interface{} if err := json.Unmarshal(libraryElement.Model, &model); err != nil { return err } if models.LibraryElementKind(libraryElement.Kind) == models.VariableElement { model["name"] = libraryElement.Name } if model["type"] != nil { libraryElement.Type = model["type"].(string) } else { model["type"] = libraryElement.Type } if model["description"] != nil { libraryElement.Description = model["description"].(string) } else { model["description"] = libraryElement.Description } syncedModel, err := json.Marshal(&model) if err != nil { return err } libraryElement.Model = syncedModel return nil } func getLibraryElement(dialect migrator.Dialect, session *db.Session, uid string, orgID int64) (LibraryElementWithMeta, error) { elements := make([]LibraryElementWithMeta, 0) sql := selectLibraryElementDTOWithMeta + ", coalesce(dashboard.title, 'General') AS folder_name" + ", coalesce(dashboard.uid, '') AS folder_uid" + getFromLibraryElementDTOWithMeta(dialect) + " LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" + " WHERE le.uid=? AND le.org_id=?" sess := session.SQL(sql, uid, orgID) err := sess.Find(&elements) if err != nil { return LibraryElementWithMeta{}, err } if len(elements) == 0 { return LibraryElementWithMeta{}, ErrLibraryElementNotFound } if len(elements) > 1 { return LibraryElementWithMeta{}, fmt.Errorf("found %d elements, while expecting at most one", len(elements)) } return elements[0], nil } // createLibraryElement adds a library element. func (l *LibraryElementService) createLibraryElement(c context.Context, signedInUser *user.SignedInUser, cmd CreateLibraryElementCommand) (LibraryElementDTO, error) { if err := l.requireSupportedElementKind(cmd.Kind); err != nil { return LibraryElementDTO{}, err } createUID := cmd.UID if len(createUID) == 0 { createUID = util.GenerateShortUID() } else { if !util.IsValidShortUID(createUID) { return LibraryElementDTO{}, errLibraryElementInvalidUID } else if util.IsShortUIDTooLong(createUID) { return LibraryElementDTO{}, errLibraryElementUIDTooLong } } element := LibraryElement{ OrgID: signedInUser.OrgID, FolderID: cmd.FolderID, UID: createUID, Name: cmd.Name, Model: cmd.Model, Version: 1, Kind: cmd.Kind, Created: time.Now(), Updated: time.Now(), CreatedBy: signedInUser.UserID, UpdatedBy: signedInUser.UserID, } if err := syncFieldsWithModel(&element); err != nil { return LibraryElementDTO{}, err } err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { if err := l.requireEditPermissionsOnFolder(c, signedInUser, cmd.FolderID); err != nil { return err } if _, err := session.Insert(&element); err != nil { if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { return errLibraryElementAlreadyExists } return err } return nil }) dto := LibraryElementDTO{ ID: element.ID, OrgID: element.OrgID, FolderID: element.FolderID, UID: element.UID, Name: element.Name, Kind: element.Kind, Type: element.Type, Description: element.Description, Model: element.Model, Version: element.Version, Meta: LibraryElementDTOMeta{ ConnectedDashboards: 0, Created: element.Created, Updated: element.Updated, CreatedBy: LibraryElementDTOMetaUser{ ID: element.CreatedBy, Name: signedInUser.Login, AvatarURL: dtos.GetGravatarUrl(signedInUser.Email), }, UpdatedBy: LibraryElementDTOMetaUser{ ID: element.UpdatedBy, Name: signedInUser.Login, AvatarURL: dtos.GetGravatarUrl(signedInUser.Email), }, }, } return dto, err } // deleteLibraryElement deletes a library element. func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedInUser *user.SignedInUser, uid string) (int64, error) { var elementID int64 err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID) if err != nil { return err } if err := l.requireEditPermissionsOnFolder(c, signedInUser, element.FolderID); err != nil { return err } // Delete any hanging/invalid connections if _, err = session.Exec(deleteInvalidConnections, element.ID); err != nil { return err } var connectionIDs []struct { ConnectionID int64 `xorm:"connection_id"` } sql := "SELECT connection_id FROM library_element_connection WHERE element_id=?" if err := session.SQL(sql, element.ID).Find(&connectionIDs); err != nil { return err } else if len(connectionIDs) > 0 { return errLibraryElementHasConnections } result, err := session.Exec("DELETE FROM library_element WHERE id=?", element.ID) if err != nil { return err } if rowsAffected, err := result.RowsAffected(); err != nil { return err } else if rowsAffected != 1 { return ErrLibraryElementNotFound } elementID = element.ID return nil }) return elementID, err } // getLibraryElements gets a Library Element where param == value func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair) ([]LibraryElementDTO, error) { libraryElements := make([]LibraryElementWithMeta, 0) err := store.WithDbSession(c, func(session *db.Session) error { builder := db.NewSqlBuilder(cfg) builder.Write(selectLibraryElementDTOWithMeta) builder.Write(", 'General' as folder_name ") builder.Write(", '' as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect())) writeParamSelectorSQL(&builder, append(params, Pair{"folder_id", 0})...) builder.Write(" UNION ") builder.Write(selectLibraryElementDTOWithMeta) builder.Write(", dashboard.title as folder_name ") builder.Write(", dashboard.uid as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect())) builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0") writeParamSelectorSQL(&builder, params...) if signedInUser.OrgRole != org.RoleAdmin { builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW) } builder.Write(` OR dashboard.id=0`) if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElements); err != nil { return err } if len(libraryElements) == 0 { return ErrLibraryElementNotFound } return nil }) if err != nil { return []LibraryElementDTO{}, err } leDtos := make([]LibraryElementDTO, len(libraryElements)) for i, libraryElement := range libraryElements { leDtos[i] = LibraryElementDTO{ ID: libraryElement.ID, OrgID: libraryElement.OrgID, FolderID: libraryElement.FolderID, FolderUID: libraryElement.FolderUID, UID: libraryElement.UID, Name: libraryElement.Name, Kind: libraryElement.Kind, Type: libraryElement.Type, Description: libraryElement.Description, Model: libraryElement.Model, Version: libraryElement.Version, Meta: LibraryElementDTOMeta{ FolderName: libraryElement.FolderName, FolderUID: libraryElement.FolderUID, ConnectedDashboards: libraryElement.ConnectedDashboards, Created: libraryElement.Created, Updated: libraryElement.Updated, CreatedBy: LibraryElementDTOMetaUser{ ID: libraryElement.CreatedBy, Name: libraryElement.CreatedByName, AvatarURL: dtos.GetGravatarUrl(libraryElement.CreatedByEmail), }, UpdatedBy: LibraryElementDTOMetaUser{ ID: libraryElement.UpdatedBy, Name: libraryElement.UpdatedByName, AvatarURL: dtos.GetGravatarUrl(libraryElement.UpdatedByEmail), }, }, } } return leDtos, nil } // getLibraryElementByUid gets a Library Element by uid. func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, UID string) (LibraryElementDTO, error) { libraryElements, err := getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: UID}}) if err != nil { return LibraryElementDTO{}, err } if len(libraryElements) > 1 { return LibraryElementDTO{}, fmt.Errorf("found %d elements, while expecting at most one", len(libraryElements)) } return libraryElements[0], nil } // getLibraryElementByName gets a Library Element by name. func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]LibraryElementDTO, error) { return getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}) } // getAllLibraryElements gets all Library Elements. func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedInUser *user.SignedInUser, query searchLibraryElementsQuery) (LibraryElementSearchResult, error) { elements := make([]LibraryElementWithMeta, 0) result := LibraryElementSearchResult{} if query.perPage <= 0 { query.perPage = 100 } if query.page <= 0 { query.page = 1 } var typeFilter []string if len(strings.TrimSpace(query.typeFilter)) > 0 { typeFilter = strings.Split(query.typeFilter, ",") } folderFilter := parseFolderFilter(query) if folderFilter.parseError != nil { return LibraryElementSearchResult{}, folderFilter.parseError } err := l.SQLStore.WithDbSession(c, func(session *db.Session) error { builder := db.NewSqlBuilder(l.Cfg) if folderFilter.includeGeneralFolder { builder.Write(selectLibraryElementDTOWithMeta) builder.Write(", 'General' as folder_name ") builder.Write(", '' as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) builder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.OrgID) writeKindSQL(query, &builder) writeSearchStringSQL(query, l.SQLStore, &builder) writeExcludeSQL(query, &builder) writeTypeFilterSQL(typeFilter, &builder) builder.Write(" UNION ") } builder.Write(selectLibraryElementDTOWithMeta) builder.Write(", dashboard.title as folder_name ") builder.Write(", dashboard.uid as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id<>0") builder.Write(` WHERE le.org_id=?`, signedInUser.OrgID) writeKindSQL(query, &builder) writeSearchStringSQL(query, l.SQLStore, &builder) writeExcludeSQL(query, &builder) writeTypeFilterSQL(typeFilter, &builder) if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil { return err } if signedInUser.OrgRole != org.RoleAdmin { builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW) } if query.sortDirection == search.SortAlphaDesc.Name { builder.Write(" ORDER BY 1 DESC") } else { builder.Write(" ORDER BY 1 ASC") } writePerPageSQL(query, l.SQLStore, &builder) if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&elements); err != nil { return err } retDTOs := make([]LibraryElementDTO, 0) for _, element := range elements { retDTOs = append(retDTOs, LibraryElementDTO{ ID: element.ID, OrgID: element.OrgID, FolderID: element.FolderID, FolderUID: element.FolderUID, UID: element.UID, Name: element.Name, Kind: element.Kind, Type: element.Type, Description: element.Description, Model: element.Model, Version: element.Version, Meta: LibraryElementDTOMeta{ FolderName: element.FolderName, FolderUID: element.FolderUID, ConnectedDashboards: element.ConnectedDashboards, Created: element.Created, Updated: element.Updated, CreatedBy: LibraryElementDTOMetaUser{ ID: element.CreatedBy, Name: element.CreatedByName, AvatarURL: dtos.GetGravatarUrl(element.CreatedByEmail), }, UpdatedBy: LibraryElementDTOMetaUser{ ID: element.UpdatedBy, Name: element.UpdatedByName, AvatarURL: dtos.GetGravatarUrl(element.UpdatedByEmail), }, }, }) } var libraryElements []LibraryElement countBuilder := db.SQLBuilder{} countBuilder.Write("SELECT * FROM library_element AS le") countBuilder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id") countBuilder.Write(` WHERE le.org_id=?`, signedInUser.OrgID) writeKindSQL(query, &countBuilder) writeSearchStringSQL(query, l.SQLStore, &countBuilder) writeExcludeSQL(query, &countBuilder) writeTypeFilterSQL(typeFilter, &countBuilder) if err := folderFilter.writeFolderFilterSQL(true, &countBuilder); err != nil { return err } if err := session.SQL(countBuilder.GetSQLString(), countBuilder.GetParams()...).Find(&libraryElements); err != nil { return err } result = LibraryElementSearchResult{ TotalCount: int64(len(libraryElements)), Elements: retDTOs, Page: query.page, PerPage: query.perPage, } return nil }) return result, err } func (l *LibraryElementService) handleFolderIDPatches(ctx context.Context, elementToPatch *LibraryElement, fromFolderID int64, toFolderID int64, user *user.SignedInUser) error { // FolderID was not provided in the PATCH request if toFolderID == -1 { toFolderID = fromFolderID } // FolderID was provided in the PATCH request if toFolderID != -1 && toFolderID != fromFolderID { if err := l.requireEditPermissionsOnFolder(ctx, user, toFolderID); err != nil { return err } } // Always check permissions for the folder where library element resides if err := l.requireEditPermissionsOnFolder(ctx, user, fromFolderID); err != nil { return err } elementToPatch.FolderID = toFolderID return nil } // patchLibraryElement updates a Library Element. func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInUser *user.SignedInUser, cmd PatchLibraryElementCommand, uid string) (LibraryElementDTO, error) { var dto LibraryElementDTO if err := l.requireSupportedElementKind(cmd.Kind); err != nil { return LibraryElementDTO{}, err } err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { elementInDB, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID) if err != nil { return err } if elementInDB.Version != cmd.Version { return errLibraryElementVersionMismatch } updateUID := cmd.UID if len(updateUID) == 0 { updateUID = uid } else if updateUID != uid { if !util.IsValidShortUID(updateUID) { return errLibraryElementInvalidUID } else if util.IsShortUIDTooLong(updateUID) { return errLibraryElementUIDTooLong } _, err := getLibraryElement(l.SQLStore.GetDialect(), session, updateUID, signedInUser.OrgID) if !errors.Is(err, ErrLibraryElementNotFound) { return errLibraryElementAlreadyExists } } var libraryElement = LibraryElement{ ID: elementInDB.ID, OrgID: signedInUser.OrgID, FolderID: cmd.FolderID, UID: updateUID, Name: cmd.Name, Kind: elementInDB.Kind, Type: elementInDB.Type, Description: elementInDB.Description, Model: cmd.Model, Version: elementInDB.Version + 1, Created: elementInDB.Created, CreatedBy: elementInDB.CreatedBy, Updated: time.Now(), UpdatedBy: signedInUser.UserID, } if cmd.Name == "" { libraryElement.Name = elementInDB.Name } if cmd.Model == nil { libraryElement.Model = elementInDB.Model } if err := l.handleFolderIDPatches(c, &libraryElement, elementInDB.FolderID, cmd.FolderID, signedInUser); err != nil { return err } if err := syncFieldsWithModel(&libraryElement); err != nil { return err } if rowsAffected, err := session.ID(elementInDB.ID).Update(&libraryElement); err != nil { if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { return errLibraryElementAlreadyExists } return err } else if rowsAffected != 1 { return ErrLibraryElementNotFound } dto = LibraryElementDTO{ ID: libraryElement.ID, OrgID: libraryElement.OrgID, FolderID: libraryElement.FolderID, UID: libraryElement.UID, Name: libraryElement.Name, Kind: libraryElement.Kind, Type: libraryElement.Type, Description: libraryElement.Description, Model: libraryElement.Model, Version: libraryElement.Version, Meta: LibraryElementDTOMeta{ ConnectedDashboards: elementInDB.ConnectedDashboards, Created: libraryElement.Created, Updated: libraryElement.Updated, CreatedBy: LibraryElementDTOMetaUser{ ID: elementInDB.CreatedBy, Name: elementInDB.CreatedByName, AvatarURL: dtos.GetGravatarUrl(elementInDB.CreatedByEmail), }, UpdatedBy: LibraryElementDTOMetaUser{ ID: libraryElement.UpdatedBy, Name: signedInUser.Login, AvatarURL: dtos.GetGravatarUrl(signedInUser.Email), }, }, } return nil }) return dto, err } // getConnections gets all connections for a Library Element. func (l *LibraryElementService) getConnections(c context.Context, signedInUser *user.SignedInUser, uid string) ([]LibraryElementConnectionDTO, error) { connections := make([]LibraryElementConnectionDTO, 0) err := l.SQLStore.WithDbSession(c, func(session *db.Session) error { element, err := getLibraryElement(l.SQLStore.GetDialect(), session, uid, signedInUser.OrgID) if err != nil { return err } var libraryElementConnections []libraryElementConnectionWithMeta builder := db.NewSqlBuilder(l.Cfg) builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email, dashboard.uid AS connection_uid") builder.Write(" FROM " + models.LibraryElementConnectionTableName + " AS lec") builder.Write(" LEFT JOIN " + l.SQLStore.GetDialect().Quote("user") + " AS u1 ON lec.created_by = u1.id") builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id") builder.Write(` WHERE lec.element_id=?`, element.ID) if signedInUser.OrgRole != org.RoleAdmin { builder.WriteDashboardPermissionFilter(signedInUser, models.PERMISSION_VIEW) } if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryElementConnections); err != nil { return err } for _, connection := range libraryElementConnections { connections = append(connections, LibraryElementConnectionDTO{ ID: connection.ID, Kind: connection.Kind, ElementID: connection.ElementID, ConnectionID: connection.ConnectionID, ConnectionUID: connection.ConnectionUID, Created: connection.Created, CreatedBy: LibraryElementDTOMetaUser{ ID: connection.CreatedBy, Name: connection.CreatedByName, AvatarURL: dtos.GetGravatarUrl(connection.CreatedByEmail), }, }) } return nil }) return connections, err } // getElementsForDashboardID gets all elements for a specific dashboard func (l *LibraryElementService) getElementsForDashboardID(c context.Context, dashboardID int64) (map[string]LibraryElementDTO, error) { libraryElementMap := make(map[string]LibraryElementDTO) err := l.SQLStore.WithDbSession(c, func(session *db.Session) error { var libraryElements []LibraryElementWithMeta sql := selectLibraryElementDTOWithMeta + ", coalesce(dashboard.title, 'General') AS folder_name" + ", coalesce(dashboard.uid, '') AS folder_uid" + getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()) + " LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" + " INNER JOIN " + models.LibraryElementConnectionTableName + " AS lce ON lce.element_id = le.id AND lce.kind=1 AND lce.connection_id=?" sess := session.SQL(sql, dashboardID) err := sess.Find(&libraryElements) if err != nil { return err } for _, element := range libraryElements { libraryElementMap[element.UID] = LibraryElementDTO{ ID: element.ID, OrgID: element.OrgID, FolderID: element.FolderID, UID: element.UID, Name: element.Name, Kind: element.Kind, Type: element.Type, Description: element.Description, Model: element.Model, Version: element.Version, Meta: LibraryElementDTOMeta{ FolderName: element.FolderName, FolderUID: element.FolderUID, ConnectedDashboards: element.ConnectedDashboards, Created: element.Created, Updated: element.Updated, CreatedBy: LibraryElementDTOMetaUser{ ID: element.CreatedBy, Name: element.CreatedByName, AvatarURL: dtos.GetGravatarUrl(element.CreatedByEmail), }, UpdatedBy: LibraryElementDTOMetaUser{ ID: element.UpdatedBy, Name: element.UpdatedByName, AvatarURL: dtos.GetGravatarUrl(element.UpdatedByEmail), }, }, } } return nil }) return libraryElementMap, err } // connectElementsToDashboardID adds connections for all elements Library Elements in a Dashboard. func (l *LibraryElementService) connectElementsToDashboardID(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error { err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { _, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID) if err != nil { return err } for _, elementUID := range elementUIDs { element, err := getLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.OrgID) if err != nil { return err } if err := l.requireViewPermissionsOnFolder(c, signedInUser, element.FolderID); err != nil { return err } connection := libraryElementConnection{ ElementID: element.ID, Kind: 1, ConnectionID: dashboardID, Created: time.Now(), CreatedBy: signedInUser.UserID, } if _, err := session.Insert(&connection); err != nil { if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { return nil } return err } } return nil }) return err } // disconnectElementsFromDashboardID deletes connections for all Library Elements in a Dashboard. func (l *LibraryElementService) disconnectElementsFromDashboardID(c context.Context, dashboardID int64) error { return l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { _, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID) if err != nil { return err } return nil }) } // deleteLibraryElementsInFolderUID deletes all Library Elements in a folder. func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Context, signedInUser *user.SignedInUser, folderUID string) error { return l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { var folderUIDs []struct { ID int64 `xorm:"id"` } err := session.SQL("SELECT id from dashboard WHERE uid=? AND org_id=? AND is_folder=?", folderUID, signedInUser.OrgID, l.SQLStore.GetDialect().BooleanStr(true)).Find(&folderUIDs) if err != nil { return err } if len(folderUIDs) == 0 { return dashboards.ErrFolderNotFound } if len(folderUIDs) != 1 { return fmt.Errorf("found %d folders, while expecting at most one", len(folderUIDs)) } folderID := folderUIDs[0].ID if err := l.requireEditPermissionsOnFolder(c, signedInUser, folderID); err != nil { return err } var connectionIDs []struct { ConnectionID int64 `xorm:"connection_id"` } sql := "SELECT lec.connection_id FROM library_element AS le" sql += " INNER JOIN " + models.LibraryElementConnectionTableName + " AS lec on le.id = lec.element_id" sql += " WHERE le.folder_id=? AND le.org_id=?" err = session.SQL(sql, folderID, signedInUser.OrgID).Find(&connectionIDs) if err != nil { return err } if len(connectionIDs) > 0 { return ErrFolderHasConnectedLibraryElements } var elementIDs []struct { ID int64 `xorm:"id"` } err = session.SQL("SELECT id from library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID).Find(&elementIDs) if err != nil { return err } for _, elementID := range elementIDs { _, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE element_id=?", elementID.ID) if err != nil { return err } } if _, err := session.Exec("DELETE FROM library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID); err != nil { return err } return nil }) }