3
0

Add (created|updated|delete)_by fields on record

Move data manipulation from repo to service layer
This commit is contained in:
Denis Arh
2019-01-21 14:42:07 +01:00
parent 4d1859cb3c
commit 2748847ee9
15 changed files with 93 additions and 83 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;
UPDATE crm_record SET created_by = owned_by;
UPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;
UPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;
+23 -12
View File
@@ -3,7 +3,6 @@ package repository
import (
"context"
"fmt"
"time"
"github.com/jmoiron/sqlx"
"github.com/lann/builder"
@@ -27,10 +26,11 @@ type (
Create(record *types.Record) (*types.Record, error)
Update(record *types.Record) (*types.Record, error)
DeleteByID(id uint64) error
Delete(record *types.Record) error
UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error)
LoadValues(IDs ...uint64) (rvs types.RecordValueSet, err error)
DeleteValues(record *types.Record) error
UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error)
}
FindResponseMeta struct {
@@ -215,7 +215,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q
), i.Value)
}
// @todo switch value for ref when doing Record/User lookup
// @todo switch value for ref when doing Record/Owner lookup
i.Value = fmt.Sprintf("rv_%s.value", i.Value)
return i, nil
@@ -258,7 +258,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q
), i.Value)
}
// @todo switch value for ref when doing Record/User lookup
// @todo switch value for ref when doing Record/Owner lookup
i.Value = fmt.Sprintf("rv_%s.value ", i.Value)
return i, nil
@@ -266,6 +266,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q
if sc, err = sp.ParseColumns(sort); err != nil {
return
}
query = query.OrderBy(sc.Strings()...)
@@ -276,8 +277,6 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q
func (r *record) Create(record *types.Record) (*types.Record, error) {
record.ID = factory.Sonyflake.NextID()
record.CreatedAt = time.Now()
record.UserID = Identity(r.Context())
if err := r.db().Replace("crm_record", record); err != nil {
return nil, errors.Wrap(err, "could not update record")
@@ -287,9 +286,6 @@ func (r *record) Create(record *types.Record) (*types.Record, error) {
}
func (r *record) Update(record *types.Record) (*types.Record, error) {
now := time.Now()
record.UpdatedAt = &now
if err := r.db().Replace("crm_record", record); err != nil {
return nil, errors.Wrap(err, "could not update record")
}
@@ -297,8 +293,23 @@ func (r *record) Update(record *types.Record) (*types.Record, error) {
return record, nil
}
func (r *record) DeleteByID(id uint64) error {
_, err := r.db().Exec("update crm_record set deleted_at=? where id=?", time.Now(), id)
func (r *record) Delete(record *types.Record) error {
_, err := r.db().Exec(
"UPDATE crm_record SET deleted_at = ?, deleted_by = ? WHERE id = ?",
record.DeletedAt,
record.DeletedBy,
record.ID,
)
return err
}
func (r *record) DeleteValues(record *types.Record) error {
_, err := r.db().Exec(
"UPDATE crm_record_value SET deleted_at = ? WHERE record_id = ?",
record.DeletedAt,
record.ID)
return err
}
+1 -1
View File
@@ -105,7 +105,7 @@ func (b *recordReportBuilder) Build(metrics, dimensions, filters string) (sql st
), i.Value)
}
// @todo switch value for ref when doing Record/User lookup
// @todo switch value for ref when doing Record/Owner lookup
i.Value = fmt.Sprintf("rv_%s.value", i.Value)
return i, nil
-6
View File
@@ -4,8 +4,6 @@ import (
"context"
"github.com/titpetric/factory"
"github.com/crusttech/crust/internal/auth"
)
type (
@@ -20,10 +18,6 @@ func DB(ctx context.Context) *factory.DB {
return factory.Database.MustGet().With(ctx)
}
func Identity(ctx context.Context) uint64 {
return auth.GetIdentityFromContext(ctx).Identity()
}
func (r *repository) With(ctx context.Context, db *factory.DB) *repository {
return &repository{
ctx: ctx,
+1 -1
View File
@@ -17,7 +17,7 @@ func TestUserRefExpanding(t *testing.T) {
ntf := &notification{}
// msg := &gomail.Message{}
usr := &types.User{ID: 72932592256548967, Email: "user@mock.ed", Name: "Mocked User"}
usr := &types.User{ID: 72932592256548967, Email: "user@mock.ed", Name: "Mocked Owner"}
usrSvc := NewMocknotificationUserService(mockCtrl)
usrSvc.EXPECT().FindByID(usr.ID).Times(1).Return(usr, nil)
+35 -33
View File
@@ -3,12 +3,14 @@ package service
import (
"context"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"github.com/crusttech/crust/crm/repository"
"github.com/crusttech/crust/crm/types"
"github.com/crusttech/crust/internal/auth"
systemService "github.com/crusttech/crust/system/service"
)
@@ -34,6 +36,7 @@ type (
Create(record *types.Record) (*types.Record, error)
Update(record *types.Record) (*types.Record, error)
DeleteByID(recordID uint64) error
// Fields(module *types.Module, record *types.Record) ([]*types.RecordValue, error)
@@ -67,10 +70,6 @@ func (svc *record) FindByID(recordID uint64) (r *types.Record, err error) {
return
}
if err = svc.preloadUsers(r); err != nil {
return
}
return
})
@@ -108,10 +107,6 @@ func (svc *record) Find(moduleID uint64, filter, sort string, page, perPage int)
return
}
if err = svc.preloadUsers(rsp.Records...); err != nil {
return
}
return
})
@@ -131,6 +126,9 @@ func (svc *record) Create(new *types.Record) (record *types.Record, err error) {
return
}
new.OwnedBy = auth.GetIdentityFromContext(svc.ctx).Identity()
new.CreatedBy = new.OwnedBy
new.CreatedAt = time.Now()
if record, err = svc.repository.Create(new); err != nil {
return
}
@@ -143,10 +141,6 @@ func (svc *record) Create(new *types.Record) (record *types.Record, err error) {
return
}
if err = svc.preloadUsers(record); err != nil {
return
}
return
})
@@ -165,9 +159,6 @@ func (svc *record) Update(updated *types.Record) (record *types.Record, err erro
return errors.Wrap(err, "nonexistent record")
}
updated.CreatedAt = record.CreatedAt
updated.UserID = record.UserID
if module, err = svc.moduleRepo.FindByID(updated.ModuleID); err != nil {
return
}
@@ -176,7 +167,11 @@ func (svc *record) Update(updated *types.Record) (record *types.Record, err erro
return
}
if record, err = svc.repository.Update(updated); err != nil {
now := time.Now()
record.UpdatedAt = &now
record.UpdatedBy = auth.GetIdentityFromContext(svc.ctx).Identity()
if record, err = svc.repository.Update(record); err != nil {
return
}
@@ -184,10 +179,6 @@ func (svc *record) Update(updated *types.Record) (record *types.Record, err erro
return
}
if err = svc.preloadUsers(record); err != nil {
return
}
return
})
@@ -198,8 +189,30 @@ func (svc *record) Update(updated *types.Record) (record *types.Record, err erro
// return s.repository.Fields(module, record)
// }
func (svc *record) DeleteByID(id uint64) error {
return svc.repository.DeleteByID(id)
func (svc *record) DeleteByID(ID uint64) (err error) {
err = svc.db.Transaction(func() (err error) {
var record *types.Record
if record, err = svc.repository.FindByID(ID); err != nil {
return errors.Wrap(err, "nonexistent record")
}
now := time.Now()
record.DeletedAt = &now
record.DeletedBy = auth.GetIdentityFromContext(svc.ctx).Identity()
if err = svc.repository.Delete(record); err != nil {
return
}
if err = svc.repository.DeleteValues(record); err != nil {
return
}
return
})
return errors.Wrap(err, "unable to delete record")
}
// Validates and filters record values
@@ -248,14 +261,3 @@ func (svc *record) preloadValues(rr ...*types.Record) error {
})
}
}
func (svc *record) preloadUsers(rr ...*types.Record) error {
if uu, err := svc.userSvc.FindByIDs(types.RecordSet(rr).UserIDs()...); err != nil {
return err
} else {
return types.RecordSet(rr).Walk(func(r *types.Record) error {
r.User = uu.FindByID(r.UserID)
return nil
})
}
}
+9 -13
View File
@@ -1,14 +1,11 @@
package types
import (
"time"
"database/sql/driver"
"encoding/json"
"time"
"github.com/jmoiron/sqlx/types"
systemTypes "github.com/crusttech/crust/system/types"
)
type (
@@ -17,16 +14,15 @@ type (
ID uint64 `json:"recordID,string" db:"id"`
ModuleID uint64 `json:"moduleID,string" db:"module_id"`
User *systemTypes.User `json:"user,omitempty" db:"-"`
UserID uint64 `json:"userID,string" db:"user_id"`
Page *Page `json:"page,omitempty"`
Values RecordValueSet `json:"values,omitempty" db:"-"`
OwnedBy uint64 `db:"owned_by" json:"ownedBy,string"`
CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"`
UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"`
CreatedBy uint64 `db:"created_by" json:"createdBy,string" `
UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty,omitempty"`
UpdatedBy uint64 `db:"updated_by" json:"updatedBy,string,omitempty" `
DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"`
DeletedBy uint64 `db:"deleted_by" json:"deletedBy,string,omitempty" `
}
// RecordValue is a stored row in the `record_value` table
@@ -154,7 +150,7 @@ func (set ModuleFieldSet) FilterByModule(moduleID uint64) (ff ModuleFieldSet) {
// IsRef tells us if value of this field be a reference to something (another record, user)?
func (f ModuleField) IsRef() bool {
return f.Kind == "Record" || f.Kind == "User"
return f.Kind == "Record" || f.Kind == "Owner"
}
// UserIDs returns a slice of user IDs from all items in the set
@@ -166,12 +162,12 @@ func (set RecordSet) UserIDs() (IDs []uint64) {
loop:
for i := range set {
for _, id := range IDs {
if id == set[i].UserID {
if id == set[i].OwnedBy {
continue loop
}
}
IDs = append(IDs, set[i].UserID)
IDs = append(IDs, set[i].OwnedBy)
}
return
+3 -3
View File
@@ -4,7 +4,7 @@ package config
// type (
// // AuthSettings holds configuration settings for auth service
// AuthSettings struct {
// User struct {
// Owner struct {
// Handle struct {
// // How long can a user's handle be
// MaxLength uint `json:"maxLength,omitempty"`
@@ -17,8 +17,8 @@ package config
// )
//
// func DefaultAuthSettings() (s AuthSettings) {
// s.User.Handle.MaxLength = 20
// s.User.Handle.Enabled = true
// s.Owner.Handle.MaxLength = 20
// s.Owner.Handle.Enabled = true
//
// return s
// }
+1 -1
View File
@@ -49,7 +49,7 @@ func TestSessions(t *testing.T) {
// check user has permissions from role
{
must(t, resources.CheckAccess("test-resource", "view", "test-session"), "User has permission, but CheckAccess reports error")
must(t, resources.CheckAccess("test-resource", "view", "test-session"), "Owner has permission, but CheckAccess reports error")
mustFail(t, resources.CheckAccess("test-resource", "delete", "test-session"))
}
+1 -1
View File
@@ -75,7 +75,7 @@ func TestMentionsExtraction(t *testing.T) {
assert(t, len(mm) == len(c.ids), "Number of extracted (%d) and expected (%d) user IDs do not match (%s)", len(mm), len(c.ids), c.text)
for _, id := range c.ids {
assert(t, len(mm.FindByUserID(id)) == 1, "User ID (%d) was not extracted (%s)", id, c.text)
assert(t, len(mm.FindByUserID(id)) == 1, "Owner ID (%d) was not extracted (%s)", id, c.text)
}
}
}
+2 -2
View File
@@ -25,7 +25,7 @@ func TestTeam(t *testing.T) {
{
u1, err := userRepo.Create(user)
assert(t, err == nil, "User.Create error: %+v", err)
assert(t, err == nil, "Owner.Create error: %+v", err)
assert(t, user.ID == u1.ID, "Changes were not stored")
}
@@ -98,6 +98,6 @@ func TestTeam(t *testing.T) {
{
err := userRepo.DeleteByID(user.ID)
assert(t, err == nil, "User.DeleteByID error: %+v", err)
assert(t, err == nil, "Owner.DeleteByID error: %+v", err)
}
}
+5 -5
View File
@@ -25,7 +25,7 @@ func TestUser(t *testing.T) {
{
uu, err := userRepo.Create(user)
assert(t, err == nil, "User.Create error: %+v", err)
assert(t, err == nil, "Owner.Create error: %+v", err)
assert(t, user.ID == uu.ID, "Changes were not stored")
}
@@ -45,14 +45,14 @@ func TestUser(t *testing.T) {
{
uu, err := userRepo.FindByID(user.ID)
assert(t, err == nil, "User.FindByID error: %+v", err)
assert(t, err == nil, "Owner.FindByID error: %+v", err)
assert(t, len(uu.Teams) == 1, "Expected 1 team, got %d", len(uu.Teams))
}
{
users, err := userRepo.Find(&types.UserFilter{Query: ""})
assert(t, err == nil, "User.Find error: %+v", err)
assert(t, len(users) == 1, "User.Find: expected 1 user, got %d", len(users))
assert(t, len(users[0].Teams) == 1, "User.Find: expected 1 team, got %d", len(users[0].Teams))
assert(t, err == nil, "Owner.Find error: %+v", err)
assert(t, len(users) == 1, "Owner.Find: expected 1 user, got %d", len(users))
assert(t, len(users[0].Teams) == 1, "Owner.Find: expected 1 team, got %d", len(users[0].Teams))
}
}
+2 -2
View File
@@ -135,7 +135,7 @@ func (svc *auth) Social(profile goth.User) (u *types.User, err error) {
c.ID,
)
// User created
// Owner created
return nil
} else if err != nil {
return err
@@ -153,7 +153,7 @@ func (svc *auth) Social(profile goth.User) (u *types.User, err error) {
profile.Provider,
)
// User loaded, carry on.
// Owner loaded, carry on.
return nil
})
}
+2 -2
View File
@@ -4,7 +4,7 @@ package service
// mockCtrl := gomock.NewController(t)
// defer mockCtrl.Finish()
//
// usr := &types.User{ID: factory.Sonyflake.NextID()}
// usr := &types.Owner{ID: factory.Sonyflake.NextID()}
//
// usrRpoMock := NewMockRepository(mockCtrl)
// usrRpoMock.EXPECT().WithCtx(gomock.Any()).AnyTimes().Return(usrRpoMock)
@@ -13,7 +13,7 @@ package service
// Times(1).
// Return(usr, nil)
//
// svc := User()
// svc := Owner()
// svc.rpo = usrRpoMock
//
// found, err := svc.FindByID(context.Background(), usr.ID)