// Transactional executes the given function in a transaction. If todo returns an error, the transaction is rolled back func Transactional(db DB, todo func(f Application) error) error { var tx Transaction var err error if tx, err = db.BeginTransaction(); err != nil { log.Error(nil, map[string]interface{}{ "err": err, }, "database BeginTransaction failed!") return errors.WithStack(err) } if err := todo(tx); err != nil { log.Debug(nil, map[string]interface{}{ "pkg": "application", }, "Rolling back the transaction...") tx.Rollback() log.Error(nil, map[string]interface{}{ "err": err, }, "database transaction failed!") return errors.WithStack(err) } log.Debug(nil, map[string]interface{}{ "pkg": "application", }, "Commit the transaction!") return tx.Commit() }
// DeleteCreatedEntities records all created entities on the gorm.DB connection // and returns a function which can be called on defer to delete created // entities in reverse order on function exit. // // In addition to that, the WIT cache is cleared as well in order to respect any // deletions made to the db. // // Usage: // // func TestDatabaseActions(t *testing.T) { // // // setup database connection // db := .... // // setup auto clean up of created entities // defer DeleteCreatedEntities(db)() // // repo := NewRepo(db) // repo.Create(X) // repo.Create(X) // repo.Create(X) // } // // Output: // // 2017/01/31 12:08:08 Deleting from x 6d143405-1232-40de-bc73-835b543cd972 // 2017/01/31 12:08:08 Deleting from x 0685068d-4934-4d9a-bac2-91eebbca9575 // 2017/01/31 12:08:08 Deleting from x 2d20944e-7952-40c1-bd15-f3fa1a70026d func DeleteCreatedEntities(db *gorm.DB) func() { hookName := "mighti:record" type entity struct { table string keyname string key interface{} } var entires []entity db.Callback().Create().After("gorm:create").Register(hookName, func(scope *gorm.Scope) { entires = append(entires, entity{table: scope.TableName(), keyname: scope.PrimaryKey(), key: scope.PrimaryKeyValue()}) }) return func() { defer db.Callback().Create().Remove(hookName) tx := db.Begin() for i := len(entires) - 1; i >= 0; i-- { entry := entires[i] log.Debug(nil, map[string]interface{}{ "pkg": "cleaner", "table": entry.table, "key": entry.key, }, "Deleting entities from %s with key %s", entry.table, entry.key) tx.Table(entry.table).Where(entry.keyname+" = ?", entry.key).Delete("") } // Delete the work item cache as well // NOTE: Feel free to add more cache freeing calls here as needed. workitem.ClearGlobalWorkItemTypeCache() tx.Commit() } }
// Query expose an open ended Query model func (m *GormIdentityRepository) Query(funcs ...func(*gorm.DB) *gorm.DB) ([]*Identity, error) { defer goa.MeasureSince([]string{"goa", "db", "identity", "query"}, time.Now()) var objs []*Identity err := m.db.Scopes(funcs...).Table(m.TableName()).Find(&objs).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, errors.WithStack(err) } log.Debug(nil, map[string]interface{}{ "pkg": "identity", "identityList": objs, }, "Identity query executed successfully!") return objs, nil }
// Save modifies a single record. func (m *GormIdentityRepository) Save(ctx context.Context, model *Identity) error { defer goa.MeasureSince([]string{"goa", "db", "identity", "save"}, time.Now()) obj, err := m.Load(ctx, model.ID) if err != nil { log.Error(ctx, map[string]interface{}{ "identityID": model.ID, "ctx": ctx, "err": err, }, "unable to update the identity") return errors.WithStack(err) } err = m.db.Model(obj).Updates(model).Error log.Debug(ctx, map[string]interface{}{ "pkg": "identity", "identityID": model.ID, }, "Identity saved!") return errors.WithStack(err) }
// Create creates a new record. func (m *GormUserRepository) Create(ctx context.Context, u *User) error { defer goa.MeasureSince([]string{"goa", "db", "user", "create"}, time.Now()) u.ID = uuid.NewV4() err := m.db.Create(u).Error if err != nil { log.Error(ctx, map[string]interface{}{ "userID": u.ID, "err": err, }, "unable to create the user") return errors.WithStack(err) } log.Debug(ctx, map[string]interface{}{ "pkg": "user", "userID": u.ID, }, "User created!") return nil }
// Save a single comment func (m *GormCommentRepository) Save(ctx context.Context, comment *Comment) (*Comment, error) { c := Comment{} tx := m.db.Where("id=?", comment.ID).First(&c) if tx.RecordNotFound() { log.Error(ctx, map[string]interface{}{ "commentID": comment.ID, }, "comment not found!") // treating this as a not found error: the fact that we're using number internal is implementation detail return nil, errors.NewNotFoundError("comment", comment.ID.String()) } if err := tx.Error; err != nil { log.Error(ctx, map[string]interface{}{ "commentID": comment.ID, "err": err, }, "comment search operation failed!") return nil, errors.NewInternalError(err.Error()) } // make sure no comment is created with an empty 'markup' value if comment.Markup == "" { comment.Markup = rendering.SystemMarkupDefault } tx = tx.Save(comment) if err := tx.Error; err != nil { log.Error(ctx, map[string]interface{}{ "commentID": comment.ID, "err": err, }, "unable to save the comment!") return nil, errors.NewInternalError(err.Error()) } log.Debug(ctx, map[string]interface{}{ "pkg": "comment", "commentID": comment.ID, }, "Comment updated!") return comment, nil }
// Create creates a new record. func (m *GormCommentRepository) Create(ctx context.Context, comment *Comment) error { defer goa.MeasureSince([]string{"goa", "db", "comment", "create"}, time.Now()) comment.ID = uuid.NewV4() // make sure no comment is created with an empty 'markup' value if comment.Markup == "" { comment.Markup = rendering.SystemMarkupDefault } if err := m.db.Create(comment).Error; err != nil { log.Error(ctx, map[string]interface{}{ "commentID": comment.ID, "err": err, }, "unable to create the comment") return errs.WithStack(err) } log.Debug(ctx, map[string]interface{}{ "pkg": "comment", "commentID": comment.ID, }, "Comment created!") return nil }
// List return all user identities func (m *GormIdentityRepository) List(ctx context.Context) (*app.IdentityArray, error) { defer goa.MeasureSince([]string{"goa", "db", "identity", "list"}, time.Now()) var rows []Identity err := m.db.Model(&Identity{}).Order("username").Find(&rows).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, errors.WithStack(err) } res := app.IdentityArray{} res.Data = make([]*app.IdentityData, len(rows)) for index, value := range rows { ident := value.ConvertIdentityFromModel() res.Data[index] = ident.Data } log.Debug(ctx, map[string]interface{}{ "pkg": "identity", "identityList": &res, }, "Identity List executed successfully!") return &res, nil }
// Delete removes a single record. func (m *GormIdentityRepository) Delete(ctx context.Context, id uuid.UUID) error { defer goa.MeasureSince([]string{"goa", "db", "identity", "delete"}, time.Now()) var obj Identity err := m.db.Delete(&obj, id).Error if err != nil { log.Error(ctx, map[string]interface{}{ "identityID": id, "err": err, }, "unable to delete the identity") return errors.WithStack(err) } log.Debug(ctx, map[string]interface{}{ "pkg": "identity", "identityID": id, }, "Identity deleted!") return nil }
// Create creates a new record. func (m *GormIdentityRepository) Create(ctx context.Context, model *Identity) error { defer goa.MeasureSince([]string{"goa", "db", "identity", "create"}, time.Now()) if model.ID == uuid.Nil { model.ID = uuid.NewV4() } err := m.db.Create(model).Error if err != nil { log.Error(ctx, map[string]interface{}{ "identityID": model.ID, "err": err, }, "unable to create the identity") return errors.WithStack(err) } log.Debug(ctx, map[string]interface{}{ "pkg": "identity", "identityID": model.ID, }, "Identity created!") return nil }
// Save modifies a single record func (m *GormUserRepository) Save(ctx context.Context, model *User) error { defer goa.MeasureSince([]string{"goa", "db", "user", "save"}, time.Now()) obj, err := m.Load(ctx, model.ID) if err != nil { log.Error(ctx, map[string]interface{}{ "userID": model.ID, "err": err, }, "unable to update user") return errors.WithStack(err) } err = m.db.Model(obj).Updates(model).Error if err != nil { return errors.WithStack(err) } log.Debug(ctx, map[string]interface{}{ "pkg": "user", "userID": model.ID, }, "User saved!") return nil }
func main() { // -------------------------------------------------------------------- // Parse flags // -------------------------------------------------------------------- var configFilePath string var printConfig bool var migrateDB bool var scheduler *remoteworkitem.Scheduler flag.StringVar(&configFilePath, "config", "", "Path to the config file to read") flag.BoolVar(&printConfig, "printConfig", false, "Prints the config (including merged environment variables) and exits") flag.BoolVar(&migrateDB, "migrateDatabase", false, "Migrates the database to the newest version and exits.") flag.Parse() // Override default -config switch with environment variable only if -config switch was // not explicitly given via the command line. configSwitchIsSet := false flag.Visit(func(f *flag.Flag) { if f.Name == "config" { configSwitchIsSet = true } }) if !configSwitchIsSet { if envConfigPath, ok := os.LookupEnv("ALMIGHTY_CONFIG_FILE_PATH"); ok { configFilePath = envConfigPath } } var err error if err = configuration.Setup(configFilePath); err != nil { logrus.Panic(nil, map[string]interface{}{ "configFilePath": configFilePath, "err": err, }, "failed to setup the configuration") } if printConfig { os.Exit(0) } // Initialized developer mode flag for the logger log.InitializeLogger(configuration.IsPostgresDeveloperModeEnabled()) printUserInfo() var db *gorm.DB for { db, err = gorm.Open("postgres", configuration.GetPostgresConfigString()) if err != nil { db.Close() log.Logger().Errorf("ERROR: Unable to open connection to database %v\n", err) log.Logger().Infof("Retrying to connect in %v...\n", configuration.GetPostgresConnectionRetrySleep()) time.Sleep(configuration.GetPostgresConnectionRetrySleep()) } else { defer db.Close() break } } if configuration.IsPostgresDeveloperModeEnabled() { db = db.Debug() } // Migrate the schema err = migration.Migrate(db.DB()) if err != nil { log.Panic(nil, map[string]interface{}{ "err": fmt.Sprintf("%+v", err), }, "failed migration") } // Nothing to here except exit, since the migration is already performed. if migrateDB { os.Exit(0) } // Make sure the database is populated with the correct types (e.g. bug etc.) if configuration.GetPopulateCommonTypes() { // set a random request ID for the context ctx, req_id := client.ContextWithRequestID(context.Background()) log.Debug(ctx, nil, "Initializing the population of the database... Request ID: %v", req_id) if err := models.Transactional(db, func(tx *gorm.DB) error { return migration.PopulateCommonTypes(ctx, tx, workitem.NewWorkItemTypeRepository(tx)) }); err != nil { log.Panic(ctx, map[string]interface{}{ "err": fmt.Sprintf("%+v", err), }, "failed to populate common types") } if err := models.Transactional(db, func(tx *gorm.DB) error { return migration.BootstrapWorkItemLinking(ctx, link.NewWorkItemLinkCategoryRepository(tx), link.NewWorkItemLinkTypeRepository(tx)) }); err != nil { log.Panic(ctx, map[string]interface{}{ "err": fmt.Sprintf("%+v", err), }, "failed to bootstap work item linking") } } // Scheduler to fetch and import remote tracker items scheduler = remoteworkitem.NewScheduler(db) defer scheduler.Stop() scheduler.ScheduleAllQueries() // Create service service := goa.New("alm") // Mount middleware service.Use(middleware.RequestID()) service.Use(middleware.LogRequest(configuration.IsPostgresDeveloperModeEnabled())) service.Use(gzip.Middleware(9)) service.Use(jsonapi.ErrorHandler(service, true)) service.Use(middleware.Recover()) service.WithLogger(goalogrus.New(log.Logger())) publicKey, err := token.ParsePublicKey(configuration.GetTokenPublicKey()) if err != nil { log.Panic(nil, map[string]interface{}{ "err": fmt.Sprintf("%+v", err), }, "failed to parse public token") } // Setup Account/Login/Security identityRepository := account.NewIdentityRepository(db) userRepository := account.NewUserRepository(db) tokenManager := token.NewManager(publicKey) app.UseJWTMiddleware(service, jwt.New(publicKey, nil, app.NewJWTSecurity())) service.Use(login.InjectTokenManager(tokenManager)) // Mount "login" controller oauth := &oauth2.Config{ ClientID: configuration.GetKeycloakClientID(), ClientSecret: configuration.GetKeycloakSecret(), Scopes: []string{"user:email"}, Endpoint: oauth2.Endpoint{ AuthURL: configuration.GetKeycloakEndpointAuth(), TokenURL: configuration.GetKeycloakEndpointToken(), }, } appDB := gormapplication.NewGormDB(db) loginService := login.NewKeycloakOAuthProvider(oauth, identityRepository, userRepository, tokenManager, appDB) loginCtrl := NewLoginController(service, loginService, tokenManager) app.MountLoginController(service, loginCtrl) // Mount "status" controller statusCtrl := NewStatusController(service, db) app.MountStatusController(service, statusCtrl) // Mount "workitem" controller workitemCtrl := NewWorkitemController(service, appDB) app.MountWorkitemController(service, workitemCtrl) // Mount "workitemtype" controller workitemtypeCtrl := NewWorkitemtypeController(service, appDB) app.MountWorkitemtypeController(service, workitemtypeCtrl) // Mount "work item link category" controller workItemLinkCategoryCtrl := NewWorkItemLinkCategoryController(service, appDB) app.MountWorkItemLinkCategoryController(service, workItemLinkCategoryCtrl) // Mount "work item link type" controller workItemLinkTypeCtrl := NewWorkItemLinkTypeController(service, appDB) app.MountWorkItemLinkTypeController(service, workItemLinkTypeCtrl) // Mount "work item link" controller workItemLinkCtrl := NewWorkItemLinkController(service, appDB) app.MountWorkItemLinkController(service, workItemLinkCtrl) // Mount "work item comments" controller workItemCommentsCtrl := NewWorkItemCommentsController(service, appDB) app.MountWorkItemCommentsController(service, workItemCommentsCtrl) // Mount "work item relationships links" controller workItemRelationshipsLinksCtrl := NewWorkItemRelationshipsLinksController(service, appDB) app.MountWorkItemRelationshipsLinksController(service, workItemRelationshipsLinksCtrl) // Mount "comments" controller commentsCtrl := NewCommentsController(service, appDB) app.MountCommentsController(service, commentsCtrl) // Mount "tracker" controller c5 := NewTrackerController(service, appDB, scheduler) app.MountTrackerController(service, c5) // Mount "trackerquery" controller c6 := NewTrackerqueryController(service, appDB, scheduler) app.MountTrackerqueryController(service, c6) // Mount "space" controller spaceCtrl := NewSpaceController(service, appDB) app.MountSpaceController(service, spaceCtrl) // Mount "user" controller userCtrl := NewUserController(service, appDB, tokenManager) app.MountUserController(service, userCtrl) // Mount "search" controller searchCtrl := NewSearchController(service, appDB) app.MountSearchController(service, searchCtrl) // Mount "indentity" controller identityCtrl := NewIdentityController(service, appDB) app.MountIdentityController(service, identityCtrl) // Mount "users" controller usersCtrl := NewUsersController(service, appDB) app.MountUsersController(service, usersCtrl) // Mount "iterations" controller iterationCtrl := NewIterationController(service, appDB) app.MountIterationController(service, iterationCtrl) // Mount "spaceiterations" controller spaceIterationCtrl := NewSpaceIterationsController(service, appDB) app.MountSpaceIterationsController(service, spaceIterationCtrl) // Mount "userspace" controller userspaceCtrl := NewUserspaceController(service, db) app.MountUserspaceController(service, userspaceCtrl) // Mount "render" controller renderCtrl := NewRenderController(service) app.MountRenderController(service, renderCtrl) // Mount "areas" controller areaCtrl := NewAreaController(service, appDB) app.MountAreaController(service, areaCtrl) spaceAreaCtrl := NewSpaceAreasController(service, appDB) app.MountSpaceAreasController(service, spaceAreaCtrl) log.Logger().Infoln("Git Commit SHA: ", Commit) log.Logger().Infoln("UTC Build Time: ", BuildTime) log.Logger().Infoln("UTC Start Time: ", StartTime) log.Logger().Infoln("Dev mode: ", configuration.IsPostgresDeveloperModeEnabled()) http.Handle("/api/", service.Mux) http.Handle("/", http.FileServer(assetFS())) http.Handle("/favicon.ico", http.NotFoundHandler()) // Start http if err := http.ListenAndServe(configuration.GetHTTPAddress(), nil); err != nil { log.Error(nil, map[string]interface{}{ "addr": configuration.GetHTTPAddress(), "err": err, }, "unable to connect to server") service.LogError("startup", "err", err) } }