activity_model.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package activity
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "reflect"
  8. "strings"
  9. "time"
  10. "github.com/qor5/admin/presets"
  11. vuetify "github.com/qor5/ui/vuetify"
  12. "github.com/qor5/web"
  13. "github.com/qor5/x/i18n"
  14. h "github.com/theplant/htmlgo"
  15. "gorm.io/gorm"
  16. )
  17. // @snippet_begin(ActivityModelBuilder)
  18. // a unique model builder is consist of typ and presetModel
  19. type ModelBuilder struct {
  20. typ reflect.Type // model type
  21. activity *ActivityBuilder // activity builder
  22. presetModel *presets.ModelBuilder // preset model builder
  23. skip uint8 // skip the prefined data operator of the presetModel
  24. keys []string // primary keys
  25. ignoredFields []string // ignored fields
  26. typeHanders map[reflect.Type]TypeHandler // type handlers
  27. link func(interface{}) string // display the model link on the admin detail page
  28. }
  29. // @snippet_end
  30. // GetType get ModelBuilder type
  31. func (mb *ModelBuilder) GetType() reflect.Type {
  32. return mb.typ
  33. }
  34. // AddKeys add keys to the model builder
  35. func (mb *ModelBuilder) AddKeys(keys ...string) *ModelBuilder {
  36. for _, key := range keys {
  37. var find bool
  38. for _, mkey := range mb.keys {
  39. if mkey == key {
  40. find = true
  41. break
  42. }
  43. }
  44. if !find {
  45. mb.keys = append(mb.keys, key)
  46. }
  47. }
  48. return mb
  49. }
  50. // SetKeys set keys for the model builder
  51. func (mb *ModelBuilder) SetKeys(keys ...string) *ModelBuilder {
  52. mb.keys = keys
  53. return mb
  54. }
  55. // SetLink set the link that linked to the modified record
  56. func (mb *ModelBuilder) SetLink(f func(interface{}) string) *ModelBuilder {
  57. mb.link = f
  58. return mb
  59. }
  60. // SkipCreate skip the create action for preset.ModelBuilder
  61. func (mb *ModelBuilder) SkipCreate() *ModelBuilder {
  62. if mb.presetModel == nil {
  63. return mb
  64. }
  65. if mb.skip&Create == 0 {
  66. mb.skip |= Create
  67. }
  68. return mb
  69. }
  70. // SkipUpdate skip the update action for preset.ModelBuilder
  71. func (mb *ModelBuilder) SkipUpdate() *ModelBuilder {
  72. if mb.presetModel == nil {
  73. return mb
  74. }
  75. if mb.skip&Update == 0 {
  76. mb.skip |= Update
  77. }
  78. return mb
  79. }
  80. // SkipDelete skip the delete action for preset.ModelBuilder
  81. func (mb *ModelBuilder) SkipDelete() *ModelBuilder {
  82. if mb.presetModel == nil {
  83. return mb
  84. }
  85. if mb.skip&Delete == 0 {
  86. mb.skip |= Delete
  87. }
  88. return mb
  89. }
  90. // EnableActivityInfoTab enable activity info tab on the given model's editing page
  91. func (mb *ModelBuilder) EnableActivityInfoTab() *ModelBuilder {
  92. if mb.presetModel == nil {
  93. return mb
  94. }
  95. editing := mb.presetModel.Editing()
  96. editing.AppendTabsPanelFunc(func(obj interface{}, ctx *web.EventContext) (c h.HTMLComponent) {
  97. logs := mb.activity.GetCustomizeActivityLogs(obj, mb.activity.getDBFromContext(ctx.R.Context()))
  98. msgr := i18n.MustGetModuleMessages(ctx.R, I18nActivityKey, Messages_en_US).(*Messages)
  99. logsvalues := reflect.Indirect(reflect.ValueOf(logs))
  100. var panels []h.HTMLComponent
  101. for i := 0; i < logsvalues.Len(); i++ {
  102. log := logsvalues.Index(i).Interface().(ActivityLogInterface)
  103. var headerText string
  104. if mb.activity.tabHeading != nil {
  105. headerText = mb.activity.tabHeading(log)
  106. } else {
  107. headerText = fmt.Sprintf("%s %s at %s", log.GetCreator(), strings.ToLower(log.GetAction()), log.GetCreatedAt().Format("2006-01-02 15:04:05 MST"))
  108. }
  109. panels = append(panels, vuetify.VExpansionPanel(
  110. vuetify.VExpansionPanelHeader(h.Span(headerText)),
  111. vuetify.VExpansionPanelContent(DiffComponent(log.GetModelDiffs(), ctx.R)),
  112. ))
  113. }
  114. return h.Components(
  115. vuetify.VTab(h.Text(msgr.Activities)),
  116. vuetify.VTabItem(
  117. vuetify.VExpansionPanels(panels...).Attr("style", "padding:10px;"),
  118. ),
  119. )
  120. })
  121. return mb
  122. }
  123. // AddIgnoredFields append ignored fields to the default ignored fields, this would not overwrite the default ignored fields
  124. func (mb *ModelBuilder) AddIgnoredFields(fields ...string) *ModelBuilder {
  125. mb.ignoredFields = append(mb.ignoredFields, fields...)
  126. return mb
  127. }
  128. // SetIgnoredFields set ignored fields to replace the default ignored fields with the new set.
  129. func (mb *ModelBuilder) SetIgnoredFields(fields ...string) *ModelBuilder {
  130. mb.ignoredFields = fields
  131. return mb
  132. }
  133. // AddTypeHanders add type handers for the model builder
  134. func (mb *ModelBuilder) AddTypeHanders(v interface{}, f TypeHandler) *ModelBuilder {
  135. if mb.typeHanders == nil {
  136. mb.typeHanders = map[reflect.Type]TypeHandler{}
  137. }
  138. mb.typeHanders[reflect.Indirect(reflect.ValueOf(v)).Type()] = f
  139. return mb
  140. }
  141. // KeysValue get model keys value
  142. func (mb *ModelBuilder) KeysValue(v interface{}) string {
  143. var (
  144. stringBuilder = strings.Builder{}
  145. reflectValue = reflect.Indirect(reflect.ValueOf(v))
  146. reflectType = reflectValue.Type()
  147. )
  148. for _, key := range mb.keys {
  149. if fields, ok := reflectType.FieldByName(key); ok {
  150. if reflectValue.FieldByName(key).IsZero() {
  151. continue
  152. }
  153. if fields.Anonymous {
  154. stringBuilder.WriteString(fmt.Sprintf("%v:", reflectValue.FieldByName(key).FieldByName(key).Interface()))
  155. } else {
  156. stringBuilder.WriteString(fmt.Sprintf("%v:", reflectValue.FieldByName(key).Interface()))
  157. }
  158. }
  159. }
  160. return strings.TrimRight(stringBuilder.String(), ":")
  161. }
  162. // AddRecords add records log
  163. func (mb *ModelBuilder) AddRecords(action string, ctx context.Context, vs ...interface{}) error {
  164. if len(vs) == 0 {
  165. return errors.New("data are empty")
  166. }
  167. var (
  168. creator = mb.activity.getCreatorFromContext(ctx)
  169. db = mb.activity.getDBFromContext(ctx)
  170. )
  171. switch action {
  172. case ActivityView:
  173. for _, v := range vs {
  174. err := mb.AddViewRecord(creator, v, db)
  175. if err != nil {
  176. return err
  177. }
  178. }
  179. case ActivityDelete:
  180. for _, v := range vs {
  181. err := mb.AddDeleteRecord(creator, v, db)
  182. if err != nil {
  183. return err
  184. }
  185. }
  186. case ActivityCreate:
  187. for _, v := range vs {
  188. err := mb.AddCreateRecord(creator, v, db)
  189. if err != nil {
  190. return err
  191. }
  192. }
  193. case ActivityEdit:
  194. for _, v := range vs {
  195. err := mb.AddEditRecord(creator, v, db)
  196. if err != nil {
  197. return err
  198. }
  199. }
  200. }
  201. return nil
  202. }
  203. // AddCustomizedRecord add customized record
  204. func (mb *ModelBuilder) AddCustomizedRecord(action string, diff bool, ctx context.Context, obj interface{}) error {
  205. var (
  206. creator = mb.activity.getCreatorFromContext(ctx)
  207. db = mb.activity.getDBFromContext(ctx)
  208. )
  209. if !diff {
  210. return mb.save(creator, action, obj, db, "")
  211. }
  212. old, ok := findOld(obj, db)
  213. if !ok {
  214. return fmt.Errorf("can't find old data for %+v ", obj)
  215. }
  216. return mb.addDiff(action, creator, old, obj, db)
  217. }
  218. // AddViewRecord add view record
  219. func (mb *ModelBuilder) AddViewRecord(creator interface{}, v interface{}, db *gorm.DB) error {
  220. return mb.save(creator, ActivityView, v, db, "")
  221. }
  222. // AddDeleteRecord add delete record
  223. func (mb *ModelBuilder) AddDeleteRecord(creator interface{}, v interface{}, db *gorm.DB) error {
  224. return mb.save(creator, ActivityDelete, v, db, "")
  225. }
  226. // AddSaverRecord will save a create log or a edit log
  227. func (mb *ModelBuilder) AddSaveRecord(creator interface{}, now interface{}, db *gorm.DB) error {
  228. old, ok := findOld(now, db)
  229. if !ok {
  230. return mb.AddCreateRecord(creator, now, db)
  231. }
  232. return mb.AddEditRecordWithOld(creator, old, now, db)
  233. }
  234. // AddCreateRecord add create record
  235. func (mb *ModelBuilder) AddCreateRecord(creator interface{}, v interface{}, db *gorm.DB) error {
  236. return mb.save(creator, ActivityCreate, v, db, "")
  237. }
  238. // AddEditRecord add edit record
  239. func (mb *ModelBuilder) AddEditRecord(creator interface{}, now interface{}, db *gorm.DB) error {
  240. old, ok := findOld(now, db)
  241. if !ok {
  242. return fmt.Errorf("can't find old data for %+v ", now)
  243. }
  244. return mb.AddEditRecordWithOld(creator, old, now, db)
  245. }
  246. // AddEditRecord add edit record
  247. func (mb *ModelBuilder) AddEditRecordWithOld(creator interface{}, old, now interface{}, db *gorm.DB) error {
  248. return mb.addDiff(ActivityEdit, creator, old, now, db)
  249. }
  250. func (mb *ModelBuilder) addDiff(action string, creator, old, now interface{}, db *gorm.DB) error {
  251. diffs, err := mb.Diff(old, now)
  252. if err != nil {
  253. return err
  254. }
  255. if len(diffs) == 0 {
  256. return nil
  257. }
  258. b, err := json.Marshal(diffs)
  259. if err != nil {
  260. return err
  261. }
  262. return mb.save(creator, ActivityEdit, now, db, string(b))
  263. }
  264. // Diff get diffs between old and now value
  265. func (mb *ModelBuilder) Diff(old, now interface{}) ([]Diff, error) {
  266. return NewDiffBuilder(mb).Diff(old, now)
  267. }
  268. // save log into db
  269. func (mb *ModelBuilder) save(creator interface{}, action string, v interface{}, db *gorm.DB, diffs string) error {
  270. var m = mb.activity.NewLogModelData()
  271. log, ok := m.(ActivityLogInterface)
  272. if !ok {
  273. return fmt.Errorf("model %T is not implement ActivityLogInterface", m)
  274. }
  275. log.SetCreatedAt(time.Now())
  276. switch user := creator.(type) {
  277. case string:
  278. log.SetCreator(user)
  279. case CreatorInterface:
  280. log.SetCreator(user.GetName())
  281. log.SetUserID(user.GetID())
  282. default:
  283. log.SetCreator("unknown")
  284. }
  285. log.SetAction(action)
  286. log.SetModelName(mb.typ.Name())
  287. log.SetModelKeys(mb.KeysValue(v))
  288. if mb.presetModel != nil && mb.presetModel.Info().URIName() != "" {
  289. log.SetModelLabel(mb.presetModel.Info().URIName())
  290. } else {
  291. log.SetModelLabel("-")
  292. }
  293. if f := mb.link; f != nil {
  294. log.SetModelLink(f(v))
  295. }
  296. if diffs == "" && action == ActivityEdit {
  297. return nil
  298. }
  299. if action == ActivityEdit {
  300. log.SetModelDiffs(diffs)
  301. }
  302. if db.Save(log).Error != nil {
  303. return db.Error
  304. }
  305. return nil
  306. }