Plan stuff. Log stuff.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
4.8 KiB

5 years ago
  1. package bolt
  2. import (
  3. "context"
  4. "github.com/gisle/stufflog/database/repositories"
  5. "github.com/gisle/stufflog/models"
  6. "github.com/gisle/stufflog/slerrors"
  7. "github.com/vmihailenco/msgpack/v4"
  8. "go.etcd.io/bbolt"
  9. )
  10. var bnActivities = []byte("Activity")
  11. type activityRepository struct {
  12. db *bbolt.DB
  13. userIdIdx *index
  14. }
  15. func (r *activityRepository) FindID(ctx context.Context, id string) (*models.Activity, error) {
  16. activity := new(models.Activity)
  17. err := r.db.View(func(tx *bbolt.Tx) error {
  18. value := tx.Bucket(bnActivities).Get(unsafeStringToBytes(id))
  19. if value == nil {
  20. return slerrors.NotFound("Activity")
  21. }
  22. err := msgpack.Unmarshal(value, activity)
  23. if err != nil {
  24. return err
  25. }
  26. return nil
  27. })
  28. if err != nil {
  29. return nil, err
  30. }
  31. return activity, nil
  32. }
  33. func (r *activityRepository) List(ctx context.Context) ([]*models.Activity, error) {
  34. activities := make([]*models.Activity, 0, 16)
  35. err := r.db.View(func(tx *bbolt.Tx) error {
  36. cursor := tx.Bucket(bnActivities).Cursor()
  37. for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
  38. activity := new(models.Activity)
  39. err := msgpack.Unmarshal(value, activity)
  40. if err != nil {
  41. return err
  42. }
  43. activities = append(activities, activity)
  44. }
  45. return nil
  46. })
  47. if err != nil {
  48. return nil, err
  49. }
  50. return activities, nil
  51. }
  52. func (r *activityRepository) ListUser(ctx context.Context, user models.User) ([]*models.Activity, error) {
  53. activities := make([]*models.Activity, 0, 16)
  54. err := r.db.View(func(tx *bbolt.Tx) error {
  55. bucket := tx.Bucket(bnActivities)
  56. ids, err := r.userIdIdx.WithTx(tx).Get(user.ID)
  57. if err != nil {
  58. return err
  59. }
  60. for _, id := range ids {
  61. value := bucket.Get(id)
  62. if value == nil {
  63. continue
  64. }
  65. activity := new(models.Activity)
  66. err := msgpack.Unmarshal(value, activity)
  67. if err != nil {
  68. return err
  69. }
  70. activities = append(activities, activity)
  71. }
  72. return nil
  73. })
  74. if err != nil {
  75. return nil, err
  76. }
  77. return activities, nil
  78. }
  79. func (r *activityRepository) Insert(ctx context.Context, activity models.Activity) error {
  80. value, err := msgpack.Marshal(&activity)
  81. if err != nil {
  82. return err
  83. }
  84. return r.db.Update(func(tx *bbolt.Tx) error {
  85. err := tx.Bucket(bnActivities).Put(unsafeStringToBytes(activity.ID), value)
  86. if err != nil {
  87. return err
  88. }
  89. err = r.userIdIdx.WithTx(tx).Set(unsafeStringToBytes(activity.ID), activity.UserID)
  90. if err != nil {
  91. return err
  92. }
  93. return nil
  94. })
  95. }
  96. func (r *activityRepository) Update(ctx context.Context, activity models.Activity, updates []*models.ActivityUpdate) (*models.Activity, error) {
  97. err := r.db.Update(func(tx *bbolt.Tx) error {
  98. bucket := tx.Bucket(bnActivities)
  99. // Re-Get to guarantee consistency.
  100. value := bucket.Get(unsafeStringToBytes(activity.ID))
  101. if value == nil {
  102. return slerrors.NotFound("Activity")
  103. }
  104. err := msgpack.Unmarshal(value, &activity)
  105. if err != nil {
  106. return err
  107. }
  108. // Perform updates
  109. didChange := false
  110. for _, update := range updates {
  111. changed, err := activity.ApplyUpdate(*update)
  112. if err != nil {
  113. return err
  114. }
  115. if changed {
  116. didChange = true
  117. }
  118. }
  119. // Put back into bucket
  120. if didChange {
  121. value, err := msgpack.Marshal(&activity)
  122. if err != nil {
  123. return err
  124. }
  125. err = tx.Bucket(bnActivities).Put(unsafeStringToBytes(activity.ID), value)
  126. if err != nil {
  127. return err
  128. }
  129. } else {
  130. return errUnchanged
  131. }
  132. return nil
  133. })
  134. if err != nil && err != errUnchanged {
  135. return nil, err
  136. }
  137. return &activity, nil
  138. }
  139. func (r *activityRepository) Remove(ctx context.Context, activity models.Activity) error {
  140. return r.db.Update(func(tx *bbolt.Tx) error {
  141. err := tx.Bucket(bnActivities).Delete(unsafeStringToBytes(activity.ID))
  142. if err != nil {
  143. return err
  144. }
  145. err = r.userIdIdx.WithTx(tx).Set(unsafeStringToBytes(activity.ID))
  146. if err != nil {
  147. return err
  148. }
  149. return nil
  150. })
  151. }
  152. func (r *activityRepository) reindex() error {
  153. return r.db.Update(func(tx *bbolt.Tx) error {
  154. cursor := tx.Bucket(bnActivities).Cursor()
  155. err := r.userIdIdx.Reset(tx)
  156. if err != nil {
  157. return err
  158. }
  159. userIdIdxTx := r.userIdIdx.WithTx(tx)
  160. for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
  161. activity := new(models.Activity)
  162. err := msgpack.Unmarshal(value, activity)
  163. if err != nil {
  164. return err
  165. }
  166. err = userIdIdxTx.Set(unsafeStringToBytes(activity.ID), activity.UserID)
  167. if err != nil {
  168. return err
  169. }
  170. }
  171. return nil
  172. })
  173. }
  174. func newActivityRepository(db *bbolt.DB) (repositories.ActivityRepository, error) {
  175. err := db.Update(func(tx *bbolt.Tx) error {
  176. _, err := tx.CreateBucketIfNotExists(bnActivities)
  177. return err
  178. })
  179. if err != nil {
  180. return nil, err
  181. }
  182. userIdIdx, err := newModelIndex(db, "Activity", "UserID")
  183. if err != nil {
  184. return nil, err
  185. }
  186. return &activityRepository{
  187. db: db,
  188. userIdIdx: userIdIdx,
  189. }, nil
  190. }