// Save updates the given tracker in storage. // returns NotFoundError, ConversionError or InternalError func (r *GormTrackerRepository) Save(ctx context.Context, t app.Tracker) (*app.Tracker, error) { res := Tracker{} id, err := strconv.ParseUint(t.ID, 10, 64) if err != nil || id == 0 { return nil, NotFoundError{entity: "tracker", ID: t.ID} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerID": id, }, "Looking for a tracker repository with id ", id) tx := r.db.First(&res, id) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "trackerID": id, }, "tracker repository not found") return nil, NotFoundError{entity: "tracker", ID: t.ID} } _, present := RemoteWorkItemImplRegistry[t.Type] // Ensure we support this remote tracker. if present != true { return nil, BadParameterError{parameter: "type", value: t.Type} } newT := Tracker{ ID: id, URL: t.URL, Type: t.Type} if err := tx.Save(&newT).Error; err != nil { log.Error(ctx, map[string]interface{}{ "trackerID": newT.ID, "err": err, }, "unable to save tracker repository") return nil, InternalError{simpleError{err.Error()}} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "tracker": newT.ID, }, "Tracker repository successfully updated") t2 := app.Tracker{ ID: strconv.FormatUint(id, 10), URL: t.URL, Type: t.Type} return &t2, nil }
// migrateToNextVersion migrates the database to the nextVersion. // If the database is already at nextVersion or higher, the nextVersion // will be set to the actual next version. func migrateToNextVersion(tx *sql.Tx, nextVersion *int64, m migrations) error { // Obtain exclusive transaction level advisory that doesn't depend on any table. // Once obtained, the lock is held for the remainder of the current transaction. // (There is no UNLOCK TABLE command; locks are always released at transaction end.) if _, err := tx.Exec("SELECT pg_advisory_xact_lock($1)", AdvisoryLockID); err != nil { return errs.Errorf("Failed to acquire lock: %s\n", err) } // Determine current version and adjust the outmost loop // iterator variable "version" currentVersion, err := getCurrentVersion(tx) if err != nil { return errs.WithStack(err) } *nextVersion = currentVersion + 1 if *nextVersion >= int64(len(m)) { // No further updates to apply (this is NOT an error) log.Info(nil, map[string]interface{}{ "pkg": "migration", "nextVersion": *nextVersion, "currentVersion": currentVersion, }, "Current version %d. Nothing to update.", currentVersion) return nil } log.Info(nil, map[string]interface{}{ "pkg": "migration", "nextVersion": *nextVersion, "currentVersion": currentVersion, }, "Attempt to update DB to version ", *nextVersion) // Apply all the updates of the next version for j := range m[*nextVersion] { if err := m[*nextVersion][j](tx); err != nil { return errs.Errorf("Failed to execute migration of step %d of version %d: %s\n", j, *nextVersion, err) } } if _, err := tx.Exec("INSERT INTO version(version) VALUES($1)", *nextVersion); err != nil { return errs.Errorf("Failed to update DB to version %d: %s\n", *nextVersion, err) } log.Info(nil, map[string]interface{}{ "pkg": "migration", "nextVersion": *nextVersion, "currentVersion": currentVersion, }, "Successfully updated DB to version ", *nextVersion) return nil }
// Delete deletes the work item link with the given id // returns NotFoundError or InternalError func (r *GormWorkItemLinkRepository) Delete(ctx context.Context, ID string) error { id, err := satoriuuid.FromString(ID) if err != nil { // treat as not found: clients don't know it must be a UUID return errors.NewNotFoundError("work item link", ID) } var link = WorkItemLink{ ID: id, } log.Info(ctx, map[string]interface{}{ "pkg": "link", "wilID": ID, }, "Deleting the work item link repository") db := r.db.Delete(&link) if db.Error != nil { log.Error(ctx, map[string]interface{}{ "wilID": ID, "err": db.Error, }, "unable to delete work item link repository") return errors.NewInternalError(db.Error.Error()) } if db.RowsAffected == 0 { return errors.NewNotFoundError("work item link", id.String()) } return nil }
// LoadTypeFromDB return work item type for the given id func (r *GormWorkItemTypeRepository) LoadTypeFromDB(ctx context.Context, name string) (*WorkItemType, error) { log.Logger().Infoln("Loading work item type", name) res, ok := cache.Get(name) if !ok { log.Info(ctx, map[string]interface{}{ "pkg": "workitem", "type": name, }, "Work item type doesn't exist in the cache. Loading from DB...") res = WorkItemType{} db := r.db.Model(&res).Where("name=?", name).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "witName": name, }, "work item type repository not found") return nil, errors.NewNotFoundError("work item type", name) } if err := db.Error; err != nil { return nil, errors.NewInternalError(err.Error()) } cache.Put(res) } return &res, nil }
// List returns tracker selected by the given criteria.Expression, starting with start (zero-based) and returning at most limit items func (r *GormTrackerRepository) List(ctx context.Context, criteria criteria.Expression, start *int, limit *int) ([]*app.Tracker, error) { where, parameters, err := workitem.Compile(criteria) if err != nil { return nil, BadParameterError{"expression", criteria} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "query": where, }, "Executing tracker repository query...") var rows []Tracker db := r.db.Where(where, parameters...) if start != nil { db = db.Offset(*start) } if limit != nil { db = db.Limit(*limit) } if err := db.Find(&rows).Error; err != nil { return nil, errors.WithStack(err) } result := make([]*app.Tracker, len(rows)) for i, tracker := range rows { t := app.Tracker{ ID: strconv.FormatUint(tracker.ID, 10), URL: tracker.URL, Type: tracker.Type} result[i] = &t } return result, nil }
// Load returns the tracker configuration for the given id // returns NotFoundError, ConversionError or InternalError func (r *GormTrackerRepository) Load(ctx context.Context, ID string) (*app.Tracker, error) { id, err := strconv.ParseUint(ID, 10, 64) if err != nil || id == 0 { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, NotFoundError{"tracker", ID} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerID": id, }, "Loading tracker repository...") res := Tracker{} tx := r.db.First(&res, id) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "trackerID": ID, }, "tracker repository not found") return nil, NotFoundError{"tracker", ID} } if tx.Error != nil { return nil, InternalError{simpleError{fmt.Sprintf("error while loading: %s", tx.Error.Error())}} } t := app.Tracker{ ID: strconv.FormatUint(res.ID, 10), URL: res.URL, Type: res.Type} return &t, nil }
// Create creates a new tracker configuration in the repository // returns BadParameterError, ConversionError or InternalError func (r *GormTrackerRepository) Create(ctx context.Context, url string, typeID string) (*app.Tracker, error) { //URL Validation isValid := govalidator.IsURL(url) if isValid != true { return nil, BadParameterError{parameter: "url", value: url} } _, present := RemoteWorkItemImplRegistry[typeID] // Ensure we support this remote tracker. if present != true { return nil, BadParameterError{parameter: "type", value: typeID} } t := Tracker{ URL: url, Type: typeID} tx := r.db if err := tx.Create(&t).Error; err != nil { return nil, InternalError{simpleError{err.Error()}} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "tracker": t, }, "Tracker reposity created") t2 := app.Tracker{ ID: strconv.FormatUint(t.ID, 10), URL: url, Type: typeID} return &t2, nil }
// Save updates the given space in the db. Version must be the same as the one in the stored version // returns NotFoundError, BadParameterError, VersionConflictError or InternalError func (r *GormRepository) Save(ctx context.Context, p *Space) (*Space, error) { pr := Space{} tx := r.db.Where("id=?", p.ID).First(&pr) oldVersion := p.Version p.Version++ if tx.RecordNotFound() { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, errors.NewNotFoundError("space", p.ID.String()) } if err := tx.Error; err != nil { return nil, errors.NewInternalError(err.Error()) } tx = tx.Where("Version = ?", oldVersion).Save(p) if err := tx.Error; err != nil { if gormsupport.IsCheckViolation(tx.Error, "spaces_name_check") { return nil, errors.NewBadParameterError("Name", p.Name).Expected("not empty") } if gormsupport.IsUniqueViolation(tx.Error, "spaces_name_idx") { return nil, errors.NewBadParameterError("Name", p.Name).Expected("unique") } return nil, errors.NewInternalError(err.Error()) } if tx.RowsAffected == 0 { return nil, errors.NewVersionConflictError("version conflict") } log.Info(ctx, map[string]interface{}{ "pkg": "space", "spaceID": p.ID, }, "space updated successfully") return p, nil }
// Load returns the tracker query for the given id // returns NotFoundError, ConversionError or InternalError func (r *GormTrackerQueryRepository) Load(ctx context.Context, ID string) (*app.TrackerQuery, error) { id, err := strconv.ParseUint(ID, 10, 64) if err != nil || id == 0 { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, NotFoundError{"tracker query", ID} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerQueryid": id, }, "Loading the tracker query") res := TrackerQuery{} if r.db.First(&res, id).RecordNotFound() { log.Error(ctx, map[string]interface{}{ "trackerID": id, }, "tracker resource not found") return nil, NotFoundError{"tracker query", ID} } tq := app.TrackerQuery{ ID: strconv.FormatUint(res.ID, 10), Query: res.Query, Schedule: res.Schedule, TrackerID: strconv.FormatUint(res.TrackerID, 10)} return &tq, nil }
// Delete implements application.WorkItemRepository func (r *UndoableWorkItemRepository) Delete(ctx context.Context, ID string) error { id, err := strconv.ParseUint(ID, 10, 64) if err != nil { // treating this as a not found error: the fact that we're using number internal is implementation detail return errors.NewNotFoundError("work item", ID) } log.Info(ctx, map[string]interface{}{ "pkg": "workitem", "id": id, }, "Loading work iteme") old := WorkItem{} db := r.wrapped.db.First(&old, id) if db.Error != nil { return errors.NewInternalError(fmt.Sprintf("could not load %s, %s", ID, db.Error.Error())) } err = r.wrapped.Delete(ctx, ID) if err == nil { r.undo.Append(func(db *gorm.DB) error { old.DeletedAt = nil db = db.Save(&old) return db.Error }) } return errs.WithStack(err) }
// Load returns the work item link type for the given ID. // Returns NotFoundError, ConversionError or InternalError func (r *GormWorkItemLinkTypeRepository) Load(ctx context.Context, ID string) (*app.WorkItemLinkTypeSingle, error) { id, err := satoriuuid.FromString(ID) if err != nil { // treat as not found: clients don't know it must be a UUID return nil, errors.NewNotFoundError("work item link type", ID) } log.Info(ctx, map[string]interface{}{ "pkg": "link", "wiltID": ID, }, "Loading work item link type") res := WorkItemLinkType{} db := r.db.Model(&res).Where("id=?", ID).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wiltID": ID, }, "work item link type not found") return nil, errors.NewNotFoundError("work item link type", id.String()) } if db.Error != nil { return nil, errors.NewInternalError(db.Error.Error()) } // Convert the created link type entry into a JSONAPI response result := ConvertLinkTypeFromModel(res) return &result, nil }
// Delete deletes the work item link category with the given id // returns NotFoundError or InternalError func (r *GormWorkItemLinkCategoryRepository) Delete(ctx context.Context, ID string) error { id, err := satoriuuid.FromString(ID) if err != nil { // treat as not found: clients don't know it must be a UUID return errors.NewNotFoundError("work item link category", ID) } var cat = WorkItemLinkCategory{ ID: id, } log.Info(ctx, map[string]interface{}{ "pkg": "link", "wilcID": ID, }, "Work item link category to delete") db := r.db.Delete(&cat) if db.Error != nil { return errors.NewInternalError(db.Error.Error()) } if db.RowsAffected == 0 { return errors.NewNotFoundError("work item link category", id.String()) } return nil }
func createOrUpdateType(typeName string, extendedTypeName *string, fields map[string]app.FieldDefinition, ctx context.Context, witr *workitem.GormWorkItemTypeRepository, db *gorm.DB) error { wit, err := witr.LoadTypeFromDB(ctx, typeName) cause := errs.Cause(err) switch cause.(type) { case errors.NotFoundError: _, err := witr.Create(ctx, extendedTypeName, typeName, fields) if err != nil { return errs.WithStack(err) } case nil: log.Info(ctx, map[string]interface{}{ "pkg": "migration", "typeName": typeName, }, "Work item type %s exists, will update/overwrite the fields only and parentPath", typeName) path := typeName convertedFields, err := workitem.TEMPConvertFieldTypesToModel(fields) if extendedTypeName != nil { log.Info(ctx, map[string]interface{}{ "pkg": "migration", "typeName": typeName, "extendedTypeName": *extendedTypeName, }, "Work item type %s extends another type %v will copy fields from the extended type", typeName, *extendedTypeName) extendedWit, err := witr.LoadTypeFromDB(ctx, *extendedTypeName) if err != nil { return errs.WithStack(err) } path = extendedWit.Path + workitem.GetTypePathSeparator() + path //load fields from the extended type err = loadFields(ctx, extendedWit, convertedFields) if err != nil { return errs.WithStack(err) } } if err != nil { return errs.WithStack(err) } wit.Fields = convertedFields wit.Path = path db = db.Save(wit) return db.Error } return nil }
// Migrate executes the required migration of the database on startup. // For each successful migration, an entry will be written into the "version" // table, that states when a certain version was reached. func Migrate(db *sql.DB) error { var err error if db == nil { return errs.Errorf("Database handle is nil\n") } m := getMigrations() var tx *sql.Tx for nextVersion := int64(0); nextVersion < int64(len(m)) && err == nil; nextVersion++ { tx, err = db.Begin() if err != nil { return errs.Errorf("Failed to start transaction: %s\n", err) } err = migrateToNextVersion(tx, &nextVersion, m) if err != nil { oldErr := err log.Info(nil, map[string]interface{}{ "pkg": "migration", "nextVersion": nextVersion, "migrations": m, "err": err, }, "Rolling back transaction due to: ", err) if err = tx.Rollback(); err != nil { log.Error(nil, map[string]interface{}{ "nextVersion": nextVersion, "migrations": m, "err": err, }, "error while rolling back transaction: ", err) return errs.Errorf("Error while rolling back transaction: %s\n", err) } return oldErr } if err = tx.Commit(); err != nil { log.Error(nil, map[string]interface{}{ "migrations": m, "err": err, }, "error during transaction commit: ", err) return errs.Errorf("Error during transaction commit: %s\n", err) } } if err != nil { log.Error(nil, map[string]interface{}{ "migrations": m, "err": err, }, "migration failed with error: ", err) return errs.Errorf("Migration failed with error: %s\n", err) } return nil }
// Create creates a new tracker query in the repository // returns BadParameterError, ConversionError or InternalError func (r *GormTrackerQueryRepository) Create(ctx context.Context, query string, schedule string, tracker string) (*app.TrackerQuery, error) { tid, err := strconv.ParseUint(tracker, 10, 64) if err != nil || tid == 0 { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, NotFoundError{"tracker", tracker} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerID": tid, }, "Tracker ID to be created") tq := TrackerQuery{ Query: query, Schedule: schedule, TrackerID: tid} tx := r.db if err := tx.Create(&tq).Error; err != nil { log.Error(ctx, map[string]interface{}{ "trackerID": tid, "trackerQuery": query, }, "unable to create the tracker query") return nil, InternalError{simpleError{err.Error()}} } tq2 := app.TrackerQuery{ ID: strconv.FormatUint(tq.ID, 10), Query: query, Schedule: schedule, TrackerID: tracker} log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerID": tid, "trackerQuery": tq, }, "Created tracker query") return &tq2, nil }
// Save updates the given work item link in storage. Version must be the same as the one int the stored version. // returns NotFoundError, VersionConflictError, ConversionError or InternalError func (r *GormWorkItemLinkRepository) Save(ctx context.Context, lt app.WorkItemLinkSingle) (*app.WorkItemLinkSingle, error) { res := WorkItemLink{} if lt.Data.ID == nil { return nil, errors.NewBadParameterError("work item link", nil) } db := r.db.Model(&res).Where("id=?", *lt.Data.ID).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wilID": *lt.Data.ID, }, "work item link not found") return nil, errors.NewNotFoundError("work item link", *lt.Data.ID) } if db.Error != nil { log.Error(ctx, map[string]interface{}{ "wilID": *lt.Data.ID, "err": db.Error, }, "unable to find work item link") return nil, errors.NewInternalError(db.Error.Error()) } if lt.Data.Attributes.Version == nil || res.Version != *lt.Data.Attributes.Version { return nil, errors.NewVersionConflictError("version conflict") } if err := ConvertLinkToModel(lt, &res); err != nil { return nil, errs.WithStack(err) } res.Version = res.Version + 1 if err := r.ValidateCorrectSourceAndTargetType(ctx, res.SourceID, res.TargetID, res.LinkTypeID); err != nil { return nil, errs.WithStack(err) } db = r.db.Save(&res) if db.Error != nil { log.Error(ctx, map[string]interface{}{ "wilID": res.ID, "err": db.Error, }, "unable to save work item link") return nil, errors.NewInternalError(db.Error.Error()) } log.Info(ctx, map[string]interface{}{ "pkg": "link", "wilID": res.ID, }, "Work item link updated") result := ConvertLinkFromModel(res) return &result, nil }
// LoadTypeFromDB return work item link type for the given ID func (r *GormWorkItemLinkTypeRepository) LoadTypeFromDBByID(ctx context.Context, ID satoriuuid.UUID) (*WorkItemLinkType, error) { log.Info(ctx, map[string]interface{}{ "pkg": "link", "wiltID": ID.String(), }, "Loading work item link type with ID ", ID) res := WorkItemLinkType{} db := r.db.Model(&res).Where("ID=?", ID.String()).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wiltID": ID.String(), }, "work item link type not found") return nil, errors.NewNotFoundError("work item link type", ID.String()) } if db.Error != nil { return nil, errors.NewInternalError(db.Error.Error()) } return &res, nil }
// LoadCategoryFromDB return work item link category for the name func (r *GormWorkItemLinkCategoryRepository) LoadCategoryFromDB(ctx context.Context, name string) (*WorkItemLinkCategory, error) { log.Info(ctx, map[string]interface{}{ "pkg": "link", "categoryName": name, }, "Loading work item link category: %s", name) res := WorkItemLinkCategory{} db := r.db.Model(&res).Where("name=?", name).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wilcName": name, }, "work item link category not found") return nil, errors.NewNotFoundError("work item link category", name) } if db.Error != nil { return nil, errors.NewInternalError(db.Error.Error()) } return &res, nil }
// Create creates a new Space in the db // returns BadParameterError or InternalError func (r *GormRepository) Create(ctx context.Context, space *Space) (*Space, error) { space.ID = satoriuuid.NewV4() tx := r.db.Create(space) if err := tx.Error; err != nil { if gormsupport.IsCheckViolation(tx.Error, "spaces_name_check") { return nil, errors.NewBadParameterError("Name", space.Name).Expected("not empty") } if gormsupport.IsUniqueViolation(tx.Error, "spaces_name_idx") { return nil, errors.NewBadParameterError("Name", space.Name).Expected("unique") } return nil, errors.NewInternalError(err.Error()) } log.Info(ctx, map[string]interface{}{ "pkg": "space", "spaceID": space.ID, }, "Space created successfully") return space, nil }
func printUserInfo() { u, err := user.Current() if err != nil { log.Warn(nil, map[string]interface{}{ "err": fmt.Sprintf("%+v", err), }, "failed to get current user") } else { log.Info(nil, map[string]interface{}{ "username": u.Username, "uuid": u.Uid, }, "Running as user name '%s' with UID %s.", u.Username, u.Uid) /* g, err := user.LookupGroupId(u.Gid) if err != nil { fmt.Printf("Failed to lookup group: %", err.Error()) } else { fmt.Printf("Running with group \"%s\" with GID %s.\n", g.Name, g.Gid) } */ } }
// LoadTypeFromDB return work item link type for the given name in the correct link category // NOTE: Two link types can coexist with different categoryIDs. func (r *GormWorkItemLinkTypeRepository) LoadTypeFromDBByNameAndCategory(ctx context.Context, name string, categoryId satoriuuid.UUID) (*WorkItemLinkType, error) { log.Info(ctx, map[string]interface{}{ "pkg": "link", "wiltName": name, "categoryId": categoryId, }, "Loading work item link type %s with category ID %s", name, categoryId.String()) res := WorkItemLinkType{} db := r.db.Model(&res).Where("name=? AND link_category_id=?", name, categoryId.String()).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wiltName": name, "categoryId": categoryId.String(), }, "work item link type not found") return nil, errors.NewNotFoundError("work item link type", name) } if db.Error != nil { return nil, errors.NewInternalError(db.Error.Error()) } return &res, nil }
func createOrUpdateWorkItemLinkCategory(ctx context.Context, linkCatRepo *link.GormWorkItemLinkCategoryRepository, name string, description string) error { cat, err := linkCatRepo.LoadCategoryFromDB(ctx, name) cause := errs.Cause(err) switch cause.(type) { case errors.NotFoundError: _, err := linkCatRepo.Create(ctx, &name, &description) if err != nil { return errs.WithStack(err) } case nil: log.Info(ctx, map[string]interface{}{ "pkg": "migration", "category": name, }, "Work item link category %s exists, will update/overwrite the description", name) cat.Description = &description linkCat := link.ConvertLinkCategoryFromModel(*cat) _, err = linkCatRepo.Save(ctx, linkCat) return errs.WithStack(err) } return nil }
func createOrUpdateWorkItemLinkType(ctx context.Context, linkCatRepo *link.GormWorkItemLinkCategoryRepository, linkTypeRepo *link.GormWorkItemLinkTypeRepository, name, description, topology, forwardName, reverseName, sourceTypeName, targetTypeName, linkCatName string) error { cat, err := linkCatRepo.LoadCategoryFromDB(ctx, linkCatName) if err != nil { return errs.WithStack(err) } linkType, err := linkTypeRepo.LoadTypeFromDBByNameAndCategory(ctx, name, cat.ID) lt := link.WorkItemLinkType{ Name: name, Description: &description, Topology: topology, ForwardName: forwardName, ReverseName: reverseName, SourceTypeName: sourceTypeName, TargetTypeName: targetTypeName, LinkCategoryID: cat.ID, } cause := errs.Cause(err) switch cause.(type) { case errors.NotFoundError: _, err := linkTypeRepo.Create(ctx, lt.Name, lt.Description, lt.SourceTypeName, lt.TargetTypeName, lt.ForwardName, lt.ReverseName, lt.Topology, lt.LinkCategoryID) if err != nil { return errs.WithStack(err) } case nil: log.Info(ctx, map[string]interface{}{ "pkg": "migration", "wilt": name, }, "Work item link type %s exists, will update/overwrite all fields", name) lt.ID = linkType.ID lt.Version = linkType.Version _, err = linkTypeRepo.Save(ctx, link.ConvertLinkTypeFromModel(lt)) return errs.WithStack(err) } return nil }
// LoadFromDB returns the work item with the given ID in model representation. func (r *GormWorkItemRepository) LoadFromDB(ctx context.Context, ID string) (*WorkItem, error) { id, err := strconv.ParseUint(ID, 10, 64) if err != nil || id == 0 { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, errors.NewNotFoundError("work item", ID) } log.Info(nil, map[string]interface{}{ "pkg": "workitem", "wiID": ID, }, "Loading work item") res := WorkItem{} tx := r.db.First(&res, id) if tx.RecordNotFound() { log.Error(nil, map[string]interface{}{ "wiID": ID, }, "work item not found") return nil, errors.NewNotFoundError("work item", ID) } if tx.Error != nil { return nil, errors.NewInternalError(tx.Error.Error()) } return &res, nil }
// Perform performs authenticatin func (keycloak *KeycloakOAuthProvider) Perform(ctx *app.AuthorizeLoginContext) error { state := ctx.Params.Get("state") code := ctx.Params.Get("code") referer := ctx.RequestData.Header.Get("Referer") if code != "" { // After redirect from oauth provider // validate known state var knownReferer string defer func() { delete(stateReferer, state) }() knownReferer = stateReferer[state] if state == "" || knownReferer == "" { log.Error(ctx, map[string]interface{}{ "state": state, "referer": knownReferer, }, "state or known referer was empty") jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized("State or known referer was empty")) return ctx.Unauthorized(jerrors) } keycloakToken, err := keycloak.config.Exchange(ctx, code) if err != nil || keycloakToken.AccessToken == "" { log.Error(ctx, map[string]interface{}{ "code": code, "err": err, }, "keycloak exchange operation failed") return redirectWithError(ctx, knownReferer, InvalidCodeError) } _, _, err = keycloak.CreateKeycloakUser(keycloakToken.AccessToken, ctx) if err != nil { log.Error(ctx, map[string]interface{}{ "token": keycloakToken.AccessToken, "err": err, }, "failed to create a user and KeyCloak identity using the access token") return redirectWithError(ctx, knownReferer, err.Error()) } referelURL, err := url.Parse(knownReferer) if err != nil { return redirectWithError(ctx, knownReferer, err.Error()) } err = encodeToken(referelURL, keycloakToken) if err != nil { return redirectWithError(ctx, knownReferer, err.Error()) } ctx.ResponseData.Header().Set("Location", referelURL.String()) return ctx.TemporaryRedirect() } // First time access, redirect to oauth provider // store referer id to state for redirect later log.Info(ctx, map[string]interface{}{ "pkg": "login", "referer": referer, }, "Got Request from!") state = uuid.NewV4().String() mapLock.Lock() defer mapLock.Unlock() stateReferer[state] = referer keycloak.config.RedirectURL = rest.AbsoluteURL(ctx.RequestData, "/api/login/authorize") redirectURL := keycloak.config.AuthCodeURL(state, oauth2.AccessTypeOnline) ctx.ResponseData.Header().Set("Location", redirectURL) return ctx.TemporaryRedirect() }
// Map a remote work item into an ALM work item and persist it into the database. func convert(db *gorm.DB, tID int, item TrackerItemContent, provider string) (*app.WorkItem, error) { remoteID := item.ID content := string(item.Content) wir := workitem.NewWorkItemRepository(db) ti := TrackerItem{Item: content, RemoteItemID: remoteID, TrackerID: uint64(tID)} // Converting the remote item to a local work item remoteTrackerItemMethodRef, ok := RemoteWorkItemImplRegistry[provider] if !ok { return nil, BadParameterError{parameter: provider, value: provider} } remoteTrackerItem, err := remoteTrackerItemMethodRef(ti) if err != nil { return nil, InternalError{simpleError{message: " Error parsing the tracker data "}} } workItem, err := Map(remoteTrackerItem, WorkItemKeyMaps[provider]) if err != nil { return nil, ConversionError{simpleError{message: " Error mapping to local work item "}} } // Get the remote item identifier ( which is currently the url ) to check if the work item exists in the database. workItemRemoteID := workItem.Fields[workitem.SystemRemoteItemID] sqlExpression := criteria.Equals(criteria.Field(workitem.SystemRemoteItemID), criteria.Literal(workItemRemoteID)) var newWorkItem *app.WorkItem // Querying the database existingWorkItems, _, err := wir.List(context.Background(), sqlExpression, nil, nil) if err != nil { return nil, err } if len(existingWorkItems) != 0 { log.Info(nil, map[string]interface{}{ "pkg": "remoteworkitem", "workitem": workItem, }, "Workitem exists, will be updated") existingWorkItem := existingWorkItems[0] for key, value := range workItem.Fields { existingWorkItem.Fields[key] = value } newWorkItem, err = wir.Save(context.Background(), *existingWorkItem) if err != nil { log.Error(nil, map[string]interface{}{ "existingWorkitem": existingWorkItem, "err": err, }, "unable to update the work item") } } else { log.Info(nil, map[string]interface{}{ "pkg": "remoteworkitem", "sqlExpression": sqlExpression, "err": err, }, "Work item not found , will now create new work item") c := workItem.Fields[workitem.SystemCreator] var creator string if c != nil { creator = c.(string) } newWorkItem, err = wir.Create(context.Background(), workitem.SystemBug, workItem.Fields, creator) if err != nil { log.Error(nil, map[string]interface{}{ "creator": creator, "workItem.Fields": workItem.Fields, "workitem.SystemBug": workitem.SystemBug, "err": err, }, "unable to create the work item") } } return newWorkItem, errors.WithStack(err) }
// Save updates the given tracker query in storage. // returns NotFoundError, ConversionError or InternalError func (r *GormTrackerQueryRepository) Save(ctx context.Context, tq app.TrackerQuery) (*app.TrackerQuery, error) { res := TrackerQuery{} id, err := strconv.ParseUint(tq.ID, 10, 64) if err != nil || id == 0 { return nil, NotFoundError{entity: "trackerquery", ID: tq.ID} } tid, err := strconv.ParseUint(tq.TrackerID, 10, 64) if err != nil || tid == 0 { // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, NotFoundError{"tracker", tq.TrackerID} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerID": id, }, "looking tracker query") tx := r.db.First(&res, id) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "trackerID": id, }, "tracker query not found") return nil, NotFoundError{entity: "TrackerQuery", ID: tq.ID} } if tx.Error != nil { return nil, InternalError{simpleError{fmt.Sprintf("could not load tracker query: %s", tx.Error.Error())}} } tx = r.db.First(&Tracker{}, tid) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "trackerID": id, }, "tracker ID not found") return nil, NotFoundError{entity: "tracker", ID: tq.TrackerID} } if tx.Error != nil { return nil, InternalError{simpleError{fmt.Sprintf("could not load tracker: %s", tx.Error.Error())}} } newTq := TrackerQuery{ ID: id, Schedule: tq.Schedule, Query: tq.Query, TrackerID: tid} if err := tx.Save(&newTq).Error; err != nil { log.Error(ctx, map[string]interface{}{ "trackerQuery": tq.Query, "trackerID": tid, "err": err, }, "unable to save the tracker query") return nil, InternalError{simpleError{err.Error()}} } log.Info(ctx, map[string]interface{}{ "pkg": "remoteworkitem", "trackerQuery": newTq, }, "Updated tracker query") t2 := app.TrackerQuery{ ID: tq.ID, Schedule: tq.Schedule, Query: tq.Query, TrackerID: tq.TrackerID} return &t2, nil }
// Save updates the given work item link category in storage. Version must be the same as the one int the stored version. // returns NotFoundError, VersionConflictError, ConversionError or InternalError func (r *GormWorkItemLinkCategoryRepository) Save(ctx context.Context, linkCat app.WorkItemLinkCategorySingle) (*app.WorkItemLinkCategorySingle, error) { res := WorkItemLinkCategory{} if linkCat.Data.ID == nil { return nil, errors.NewBadParameterError("data.id", linkCat.Data.ID) } id, err := satoriuuid.FromString(*linkCat.Data.ID) if err != nil { log.Error(ctx, map[string]interface{}{ "wilcID": *linkCat.Data.ID, "err": err, }, "error when converting %s to UUID: %s", *linkCat.Data.ID, err.Error()) // treat as not found: clients don't know it must be a UUID return nil, errors.NewNotFoundError("work item link category", id.String()) } if linkCat.Data.Type != EndpointWorkItemLinkCategories { return nil, errors.NewBadParameterError("data.type", linkCat.Data.Type).Expected(EndpointWorkItemLinkCategories) } // If the name is not nil, it MUST NOT be empty if linkCat.Data.Attributes.Name != nil && *linkCat.Data.Attributes.Name == "" { return nil, errors.NewBadParameterError("data.attributes.name", *linkCat.Data.Attributes.Name) } db := r.db.Model(&res).Where("id=?", *linkCat.Data.ID).First(&res) if db.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wilcID": *linkCat.Data.ID, }, "work item link category not found") return nil, errors.NewNotFoundError("work item link category", id.String()) } if db.Error != nil { log.Error(ctx, map[string]interface{}{ "wilcID": *linkCat.Data.ID, "err": db.Error, }, "unable to find work item link category") return nil, errors.NewInternalError(db.Error.Error()) } if linkCat.Data.Attributes.Version == nil || res.Version != *linkCat.Data.Attributes.Version { return nil, errors.NewVersionConflictError("version conflict") } newLinkCat := WorkItemLinkCategory{ ID: id, Version: *linkCat.Data.Attributes.Version + 1, } if linkCat.Data.Attributes.Name != nil { newLinkCat.Name = *linkCat.Data.Attributes.Name } if linkCat.Data.Attributes.Description != nil { newLinkCat.Description = linkCat.Data.Attributes.Description } db = db.Save(&newLinkCat) if db.Error != nil { log.Error(ctx, map[string]interface{}{ "wilcID": newLinkCat.ID, "err": db.Error, }, "unable to save work item link category repository") return nil, errors.NewInternalError(db.Error.Error()) } log.Info(ctx, map[string]interface{}{ "pkg": "link", "wilcID": newLinkCat.ID, "newLinkCategory": newLinkCat, }, "Work item link category updated") result := ConvertLinkCategoryFromModel(newLinkCat) return &result, nil }
// Save updates the given work item in storage. Version must be the same as the one int the stored version // returns NotFoundError, VersionConflictError, ConversionError or InternalError func (r *GormWorkItemRepository) Save(ctx context.Context, wi app.WorkItem) (*app.WorkItem, error) { res := WorkItem{} id, err := strconv.ParseUint(wi.ID, 10, 64) if err != nil || id == 0 { return nil, errors.NewNotFoundError("work item", wi.ID) } log.Info(ctx, map[string]interface{}{ "pkg": "workitem", "wiID": wi.ID, }, "Looking for id for the work item repository") tx := r.db.First(&res, id) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "wiID": wi.ID, }, "work item repository not found") return nil, errors.NewNotFoundError("work item", wi.ID) } if tx.Error != nil { return nil, errors.NewInternalError(err.Error()) } if res.Version != wi.Version { return nil, errors.NewVersionConflictError("version conflict") } wiType, err := r.wir.LoadTypeFromDB(ctx, wi.Type) if err != nil { return nil, errors.NewBadParameterError("Type", wi.Type) } res.Version = res.Version + 1 res.Type = wi.Type res.Fields = Fields{} for fieldName, fieldDef := range wiType.Fields { if fieldName == SystemCreatedAt { continue } fieldValue := wi.Fields[fieldName] var err error res.Fields[fieldName], err = fieldDef.ConvertToModel(fieldName, fieldValue) if err != nil { return nil, errors.NewBadParameterError(fieldName, fieldValue) } } tx = tx.Where("Version = ?", wi.Version).Save(&res) if err := tx.Error; err != nil { log.Error(ctx, map[string]interface{}{ "wiID": wi.ID, "err": err, }, "unable to save the work item repository") return nil, errors.NewInternalError(err.Error()) } if tx.RowsAffected == 0 { return nil, errors.NewVersionConflictError("version conflict") } log.Info(ctx, map[string]interface{}{ "pkg": "workitem", "wiID": wi.ID, }, "Updated work item repository") return convertWorkItemModelToApp(wiType, &res) }
// extracted this function from List() in order to close the rows object with "defer" for more readability // workaround for https://github.com/lib/pq/issues/81 func (r *GormWorkItemRepository) listItemsFromDB(ctx context.Context, criteria criteria.Expression, start *int, limit *int) ([]WorkItem, uint64, error) { where, parameters, compileError := Compile(criteria) if compileError != nil { return nil, 0, errors.NewBadParameterError("expression", criteria) } log.Info(ctx, map[string]interface{}{ "pkg": "workitem", "where": where, "parameters": parameters, }, "Executing query : '%s' with params %v", where, parameters) db := r.db.Model(&WorkItem{}).Where(where, parameters...) orgDB := db if start != nil { if *start < 0 { return nil, 0, errors.NewBadParameterError("start", *start) } db = db.Offset(*start) } if limit != nil { if *limit <= 0 { return nil, 0, errors.NewBadParameterError("limit", *limit) } db = db.Limit(*limit) } db = db.Select("count(*) over () as cnt2 , *") rows, err := db.Rows() if err != nil { return nil, 0, errs.WithStack(err) } defer rows.Close() result := []WorkItem{} columns, err := rows.Columns() if err != nil { return nil, 0, errors.NewInternalError(err.Error()) } // need to set up a result for Scan() in order to extract total count. var count uint64 var ignore interface{} columnValues := make([]interface{}, len(columns)) for index := range columnValues { columnValues[index] = &ignore } columnValues[0] = &count first := true for rows.Next() { value := WorkItem{} db.ScanRows(rows, &value) if first { first = false if err = rows.Scan(columnValues...); err != nil { return nil, 0, errors.NewInternalError(err.Error()) } } result = append(result, value) } if first { // means 0 rows were returned from the first query (maybe becaus of offset outside of total count), // need to do a count(*) to find out total orgDB := orgDB.Select("count(*)") rows2, err := orgDB.Rows() defer rows2.Close() if err != nil { return nil, 0, errs.WithStack(err) } rows2.Next() // count(*) will always return a row rows2.Scan(&count) } return result, count, nil }