123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- package activity
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "time"
- "github.com/qor5/admin/presets"
- vuetify "github.com/qor5/ui/vuetify"
- "github.com/qor5/web"
- "github.com/qor5/x/i18n"
- h "github.com/theplant/htmlgo"
- "gorm.io/gorm"
- )
- // @snippet_begin(ActivityModelBuilder)
- // a unique model builder is consist of typ and presetModel
- type ModelBuilder struct {
- typ reflect.Type // model type
- activity *ActivityBuilder // activity builder
- presetModel *presets.ModelBuilder // preset model builder
- skip uint8 // skip the prefined data operator of the presetModel
- keys []string // primary keys
- ignoredFields []string // ignored fields
- typeHanders map[reflect.Type]TypeHandler // type handlers
- link func(interface{}) string // display the model link on the admin detail page
- }
- // @snippet_end
- // GetType get ModelBuilder type
- func (mb *ModelBuilder) GetType() reflect.Type {
- return mb.typ
- }
- // AddKeys add keys to the model builder
- func (mb *ModelBuilder) AddKeys(keys ...string) *ModelBuilder {
- for _, key := range keys {
- var find bool
- for _, mkey := range mb.keys {
- if mkey == key {
- find = true
- break
- }
- }
- if !find {
- mb.keys = append(mb.keys, key)
- }
- }
- return mb
- }
- // SetKeys set keys for the model builder
- func (mb *ModelBuilder) SetKeys(keys ...string) *ModelBuilder {
- mb.keys = keys
- return mb
- }
- // SetLink set the link that linked to the modified record
- func (mb *ModelBuilder) SetLink(f func(interface{}) string) *ModelBuilder {
- mb.link = f
- return mb
- }
- // SkipCreate skip the create action for preset.ModelBuilder
- func (mb *ModelBuilder) SkipCreate() *ModelBuilder {
- if mb.presetModel == nil {
- return mb
- }
- if mb.skip&Create == 0 {
- mb.skip |= Create
- }
- return mb
- }
- // SkipUpdate skip the update action for preset.ModelBuilder
- func (mb *ModelBuilder) SkipUpdate() *ModelBuilder {
- if mb.presetModel == nil {
- return mb
- }
- if mb.skip&Update == 0 {
- mb.skip |= Update
- }
- return mb
- }
- // SkipDelete skip the delete action for preset.ModelBuilder
- func (mb *ModelBuilder) SkipDelete() *ModelBuilder {
- if mb.presetModel == nil {
- return mb
- }
- if mb.skip&Delete == 0 {
- mb.skip |= Delete
- }
- return mb
- }
- // EnableActivityInfoTab enable activity info tab on the given model's editing page
- func (mb *ModelBuilder) EnableActivityInfoTab() *ModelBuilder {
- if mb.presetModel == nil {
- return mb
- }
- editing := mb.presetModel.Editing()
- editing.AppendTabsPanelFunc(func(obj interface{}, ctx *web.EventContext) (c h.HTMLComponent) {
- logs := mb.activity.GetCustomizeActivityLogs(obj, mb.activity.getDBFromContext(ctx.R.Context()))
- msgr := i18n.MustGetModuleMessages(ctx.R, I18nActivityKey, Messages_en_US).(*Messages)
- logsvalues := reflect.Indirect(reflect.ValueOf(logs))
- var panels []h.HTMLComponent
- for i := 0; i < logsvalues.Len(); i++ {
- log := logsvalues.Index(i).Interface().(ActivityLogInterface)
- var headerText string
- if mb.activity.tabHeading != nil {
- headerText = mb.activity.tabHeading(log)
- } else {
- headerText = fmt.Sprintf("%s %s at %s", log.GetCreator(), strings.ToLower(log.GetAction()), log.GetCreatedAt().Format("2006-01-02 15:04:05 MST"))
- }
- panels = append(panels, vuetify.VExpansionPanel(
- vuetify.VExpansionPanelHeader(h.Span(headerText)),
- vuetify.VExpansionPanelContent(DiffComponent(log.GetModelDiffs(), ctx.R)),
- ))
- }
- return h.Components(
- vuetify.VTab(h.Text(msgr.Activities)),
- vuetify.VTabItem(
- vuetify.VExpansionPanels(panels...).Attr("style", "padding:10px;"),
- ),
- )
- })
- return mb
- }
- // AddIgnoredFields append ignored fields to the default ignored fields, this would not overwrite the default ignored fields
- func (mb *ModelBuilder) AddIgnoredFields(fields ...string) *ModelBuilder {
- mb.ignoredFields = append(mb.ignoredFields, fields...)
- return mb
- }
- // SetIgnoredFields set ignored fields to replace the default ignored fields with the new set.
- func (mb *ModelBuilder) SetIgnoredFields(fields ...string) *ModelBuilder {
- mb.ignoredFields = fields
- return mb
- }
- // AddTypeHanders add type handers for the model builder
- func (mb *ModelBuilder) AddTypeHanders(v interface{}, f TypeHandler) *ModelBuilder {
- if mb.typeHanders == nil {
- mb.typeHanders = map[reflect.Type]TypeHandler{}
- }
- mb.typeHanders[reflect.Indirect(reflect.ValueOf(v)).Type()] = f
- return mb
- }
- // KeysValue get model keys value
- func (mb *ModelBuilder) KeysValue(v interface{}) string {
- var (
- stringBuilder = strings.Builder{}
- reflectValue = reflect.Indirect(reflect.ValueOf(v))
- reflectType = reflectValue.Type()
- )
- for _, key := range mb.keys {
- if fields, ok := reflectType.FieldByName(key); ok {
- if reflectValue.FieldByName(key).IsZero() {
- continue
- }
- if fields.Anonymous {
- stringBuilder.WriteString(fmt.Sprintf("%v:", reflectValue.FieldByName(key).FieldByName(key).Interface()))
- } else {
- stringBuilder.WriteString(fmt.Sprintf("%v:", reflectValue.FieldByName(key).Interface()))
- }
- }
- }
- return strings.TrimRight(stringBuilder.String(), ":")
- }
- // AddRecords add records log
- func (mb *ModelBuilder) AddRecords(action string, ctx context.Context, vs ...interface{}) error {
- if len(vs) == 0 {
- return errors.New("data are empty")
- }
- var (
- creator = mb.activity.getCreatorFromContext(ctx)
- db = mb.activity.getDBFromContext(ctx)
- )
- switch action {
- case ActivityView:
- for _, v := range vs {
- err := mb.AddViewRecord(creator, v, db)
- if err != nil {
- return err
- }
- }
- case ActivityDelete:
- for _, v := range vs {
- err := mb.AddDeleteRecord(creator, v, db)
- if err != nil {
- return err
- }
- }
- case ActivityCreate:
- for _, v := range vs {
- err := mb.AddCreateRecord(creator, v, db)
- if err != nil {
- return err
- }
- }
- case ActivityEdit:
- for _, v := range vs {
- err := mb.AddEditRecord(creator, v, db)
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- // AddCustomizedRecord add customized record
- func (mb *ModelBuilder) AddCustomizedRecord(action string, diff bool, ctx context.Context, obj interface{}) error {
- var (
- creator = mb.activity.getCreatorFromContext(ctx)
- db = mb.activity.getDBFromContext(ctx)
- )
- if !diff {
- return mb.save(creator, action, obj, db, "")
- }
- old, ok := findOld(obj, db)
- if !ok {
- return fmt.Errorf("can't find old data for %+v ", obj)
- }
- return mb.addDiff(action, creator, old, obj, db)
- }
- // AddViewRecord add view record
- func (mb *ModelBuilder) AddViewRecord(creator interface{}, v interface{}, db *gorm.DB) error {
- return mb.save(creator, ActivityView, v, db, "")
- }
- // AddDeleteRecord add delete record
- func (mb *ModelBuilder) AddDeleteRecord(creator interface{}, v interface{}, db *gorm.DB) error {
- return mb.save(creator, ActivityDelete, v, db, "")
- }
- // AddSaverRecord will save a create log or a edit log
- func (mb *ModelBuilder) AddSaveRecord(creator interface{}, now interface{}, db *gorm.DB) error {
- old, ok := findOld(now, db)
- if !ok {
- return mb.AddCreateRecord(creator, now, db)
- }
- return mb.AddEditRecordWithOld(creator, old, now, db)
- }
- // AddCreateRecord add create record
- func (mb *ModelBuilder) AddCreateRecord(creator interface{}, v interface{}, db *gorm.DB) error {
- return mb.save(creator, ActivityCreate, v, db, "")
- }
- // AddEditRecord add edit record
- func (mb *ModelBuilder) AddEditRecord(creator interface{}, now interface{}, db *gorm.DB) error {
- old, ok := findOld(now, db)
- if !ok {
- return fmt.Errorf("can't find old data for %+v ", now)
- }
- return mb.AddEditRecordWithOld(creator, old, now, db)
- }
- // AddEditRecord add edit record
- func (mb *ModelBuilder) AddEditRecordWithOld(creator interface{}, old, now interface{}, db *gorm.DB) error {
- return mb.addDiff(ActivityEdit, creator, old, now, db)
- }
- func (mb *ModelBuilder) addDiff(action string, creator, old, now interface{}, db *gorm.DB) error {
- diffs, err := mb.Diff(old, now)
- if err != nil {
- return err
- }
- if len(diffs) == 0 {
- return nil
- }
- b, err := json.Marshal(diffs)
- if err != nil {
- return err
- }
- return mb.save(creator, ActivityEdit, now, db, string(b))
- }
- // Diff get diffs between old and now value
- func (mb *ModelBuilder) Diff(old, now interface{}) ([]Diff, error) {
- return NewDiffBuilder(mb).Diff(old, now)
- }
- // save log into db
- func (mb *ModelBuilder) save(creator interface{}, action string, v interface{}, db *gorm.DB, diffs string) error {
- var m = mb.activity.NewLogModelData()
- log, ok := m.(ActivityLogInterface)
- if !ok {
- return fmt.Errorf("model %T is not implement ActivityLogInterface", m)
- }
- log.SetCreatedAt(time.Now())
- switch user := creator.(type) {
- case string:
- log.SetCreator(user)
- case CreatorInterface:
- log.SetCreator(user.GetName())
- log.SetUserID(user.GetID())
- default:
- log.SetCreator("unknown")
- }
- log.SetAction(action)
- log.SetModelName(mb.typ.Name())
- log.SetModelKeys(mb.KeysValue(v))
- if mb.presetModel != nil && mb.presetModel.Info().URIName() != "" {
- log.SetModelLabel(mb.presetModel.Info().URIName())
- } else {
- log.SetModelLabel("-")
- }
- if f := mb.link; f != nil {
- log.SetModelLink(f(v))
- }
- if diffs == "" && action == ActivityEdit {
- return nil
- }
- if action == ActivityEdit {
- log.SetModelDiffs(diffs)
- }
- if db.Save(log).Error != nil {
- return db.Error
- }
- return nil
- }
|