From 70a5042db597f5bb9eba91f5e69b18b23d7cc803 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Jul 2018 15:24:35 +0200 Subject: [PATCH] SAM types, handlers, services, repositories implemented --- sam/docs/src/spec.json | 2 + sam/docs/src/spec/user.json | 10 +++ sam/repository/channel.go | 104 +++++++++++++++++++++++++ sam/repository/error.go | 14 ++++ sam/repository/generics.go | 37 +++++++++ sam/repository/organisation.go | 104 +++++++++++++++++++++++++ sam/repository/team.go | 112 +++++++++++++++++++++++++++ sam/repository/user.go | 121 +++++++++++++++++++++++++++++ sam/rest/channel.go | 129 ++++++++----------------------- sam/rest/organisation.go | 118 +++++++++------------------- sam/rest/rest.go | 20 +++++ sam/rest/team.go | 120 ++++++++++------------------ sam/rest/user.go | 109 ++++---------------------- sam/service/channel.go | 79 +++++++++++++++++++ sam/service/error.go | 9 +++ sam/service/organisation.go | 74 ++++++++++++++++++ sam/service/service.go | 20 +++++ sam/service/team.go | 85 ++++++++++++++++++++ sam/service/user.go | 102 ++++++++++++++++++++---- sam/types/channel_filter.go | 7 ++ sam/types/organisation_filter.go | 7 ++ sam/types/team_filter.go | 7 ++ sam/types/user.go | 34 ++++++-- sam/types/user_filter.go | 7 ++ 24 files changed, 1058 insertions(+), 373 deletions(-) create mode 100644 sam/repository/channel.go create mode 100644 sam/repository/error.go create mode 100644 sam/repository/generics.go create mode 100644 sam/repository/organisation.go create mode 100644 sam/repository/team.go create mode 100644 sam/repository/user.go create mode 100644 sam/rest/rest.go create mode 100644 sam/service/channel.go create mode 100644 sam/service/error.go create mode 100644 sam/service/organisation.go create mode 100644 sam/service/service.go create mode 100644 sam/service/team.go create mode 100644 sam/types/channel_filter.go create mode 100644 sam/types/organisation_filter.go create mode 100644 sam/types/team_filter.go create mode 100644 sam/types/user_filter.go diff --git a/sam/docs/src/spec.json b/sam/docs/src/spec.json index 9696077ab..19856ec49 100644 --- a/sam/docs/src/spec.json +++ b/sam/docs/src/spec.json @@ -411,6 +411,8 @@ "fields": [ { "type": "uint64", "name": "ID" }, { "type": "string", "name": "Username" }, + { "type": "interface{}", "name": "Meta", "tag": "json:\"-\"" }, + { "type": "uint64", "name": "OrganisationID", "dbname": "rel_organisation" }, { "type": "[]byte", "name": "Password", "tag": "json:\"-\"", "complex": true }, { "type": "*time.Time", "name": "SuspendedAt", "tag": "json:\",omitempty\"", "complex": true }, { "type": "*time.Time", "name": "DeletedAt", "tag": "json:\",omitempty\"", "complex": true } diff --git a/sam/docs/src/spec/user.json b/sam/docs/src/spec/user.json index 131b1ec15..ed62c4da0 100644 --- a/sam/docs/src/spec/user.json +++ b/sam/docs/src/spec/user.json @@ -13,6 +13,16 @@ "name": "Username", "type": "string" }, + { + "name": "Meta", + "tag": "json:\"-\"", + "type": "interface{}" + }, + { + "dbname": "rel_organisation", + "name": "OrganisationID", + "type": "uint64" + }, { "complex": true, "name": "Password", diff --git a/sam/repository/channel.go b/sam/repository/channel.go new file mode 100644 index 000000000..873cab14d --- /dev/null +++ b/sam/repository/channel.go @@ -0,0 +1,104 @@ +package repository + +import ( + "context" + "github.com/crusttech/crust/sam/types" + "github.com/titpetric/factory" + "time" +) + +const ( + sqlChannelScope = "deleted_at IS NULL AND archived_at IS NULL" + + ErrChannelNotFound = repositoryError("ChannelNotFound") +) + +type ( + channel struct{} +) + +func Channel() channel { + return channel{} +} + +func (r channel) FindById(ctx context.Context, id uint64) (*types.Channel, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.Channel{} + if err := db.Get(mod, "SELECT * FROM channels WHERE id = ? AND "+sqlChannelScope, id); err != nil { + return nil, ErrDatabaseError + } else if mod.ID == 0 { + return nil, ErrChannelNotFound + } else { + return mod, nil + } +} + +func (r channel) Find(ctx context.Context, filter *types.ChannelFilter) ([]*types.Channel, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + var params = make([]interface{}, 0) + sql := "SELECT * FROM channels WHERE " + sqlChannelScope + + if filter != nil { + if filter.Query != "" { + sql += "AND name LIKE ?" + params = append(params, filter.Query+"%") + } + } + + sql += " ORDER BY name ASC" + + rval := make([]*types.Channel, 0) + if err := db.Select(&rval, sql, params...); err != nil { + panic(err) + return nil, ErrDatabaseError + } else { + return rval, nil + } +} + +func (r channel) Create(ctx context.Context, mod *types.Channel) (*types.Channel, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod.SetID(factory.Sonyflake.NextID()) + if err := db.Insert("channels", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r channel) Update(ctx context.Context, mod *types.Channel) (*types.Channel, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + if err := db.Replace("channels", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r channel) Archive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "channels", "archived_at", time.Now(), id) +} + +func (r channel) Unarchive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "channels", "archived_at", nil, id) +} + +func (r channel) Delete(ctx context.Context, id uint64) error { + return simpleDelete(ctx, "channels", id) +} diff --git a/sam/repository/error.go b/sam/repository/error.go new file mode 100644 index 000000000..bb9751074 --- /dev/null +++ b/sam/repository/error.go @@ -0,0 +1,14 @@ +package repository + +type ( + repositoryError string +) + +const ( + ErrDatabaseError = repositoryError("DatabaseError") + ErrNotImplemented = repositoryError("NotImplemented") +) + +func (e repositoryError) Error() string { + return "crust.sam.repository." + string(e) +} diff --git a/sam/repository/generics.go b/sam/repository/generics.go new file mode 100644 index 000000000..ac0300aa5 --- /dev/null +++ b/sam/repository/generics.go @@ -0,0 +1,37 @@ +package repository + +import ( + "context" + "fmt" + "github.com/titpetric/factory" +) + +func simpleUpdate(ctx context.Context, tableName, columnName string, value interface{}, id uint64) error { + db, err := factory.Database.Get() + if err != nil { + return ErrDatabaseError + } + + sql := fmt.Sprintf("UPDATE %s SET %s = ? WHERE id = ?", tableName, columnName) + + if _, err := db.Exec(sql, value, id); err != nil { + return ErrDatabaseError + } else { + return nil + } +} + +func simpleDelete(ctx context.Context, tableName string, id uint64) error { + db, err := factory.Database.Get() + if err != nil { + return ErrDatabaseError + } + + sql := fmt.Sprintf("DELETE %s WHERE id = ?", tableName) + + if _, err := db.Exec(sql, id); err != nil { + return ErrDatabaseError + } else { + return nil + } +} diff --git a/sam/repository/organisation.go b/sam/repository/organisation.go new file mode 100644 index 000000000..d085be21e --- /dev/null +++ b/sam/repository/organisation.go @@ -0,0 +1,104 @@ +package repository + +import ( + "context" + "github.com/crusttech/crust/sam/types" + "github.com/titpetric/factory" + "time" +) + +const ( + sqlOrganisationScope = "deleted_at IS NULL AND archived_at IS NULL" + + ErrOrganisationNotFound = repositoryError("OrganisationNotFound") +) + +type ( + organisation struct{} +) + +func Organisation() organisation { + return organisation{} +} + +func (r organisation) FindById(ctx context.Context, id uint64) (*types.Organisation, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.Organisation{} + if err := db.Get(mod, "SELECT * FROM organisations WHERE id = ? AND "+sqlOrganisationScope, id); err != nil { + return nil, ErrDatabaseError + } else if mod.ID == 0 { + return nil, ErrOrganisationNotFound + } else { + return mod, nil + } +} + +func (r organisation) Find(ctx context.Context, filter *types.OrganisationFilter) ([]*types.Organisation, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + var params = make([]interface{}, 0) + sql := "SELECT * FROM organisations WHERE " + sqlOrganisationScope + + if filter != nil { + if filter.Query != "" { + sql += "AND ame LIKE ?" + params = append(params, filter.Query+"%") + } + } + + sql += " ORDER BY name ASC" + + rval := make([]*types.Organisation, 0) + if err := db.Select(&rval, sql, params...); err != nil { + panic(err) + return nil, ErrDatabaseError + } else { + return rval, nil + } +} + +func (r organisation) Create(ctx context.Context, mod *types.Organisation) (*types.Organisation, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod.SetID(factory.Sonyflake.NextID()) + if err := db.Insert("organisations", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r organisation) Update(ctx context.Context, mod *types.Organisation) (*types.Organisation, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + if err := db.Replace("organisations", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r organisation) Archive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "organisations", "archived_at", time.Now(), id) +} + +func (r organisation) Unarchive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "organisations", "archived_at", nil, id) +} + +func (r organisation) Delete(ctx context.Context, id uint64) error { + return simpleDelete(ctx, "organisations", id) +} diff --git a/sam/repository/team.go b/sam/repository/team.go new file mode 100644 index 000000000..0d37786c2 --- /dev/null +++ b/sam/repository/team.go @@ -0,0 +1,112 @@ +package repository + +import ( + "context" + "github.com/crusttech/crust/sam/types" + "github.com/titpetric/factory" + "time" +) + +const ( + sqlTeamScope = "deleted_at IS NULL AND archived_at IS NULL" + + ErrTeamNotFound = repositoryError("TeamNotFound") +) + +type ( + team struct{} +) + +func Team() team { + return team{} +} + +func (r team) FindById(ctx context.Context, id uint64) (*types.Team, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.Team{} + if err := db.Get(mod, "SELECT * FROM teams WHERE id = ? AND "+sqlTeamScope, id); err != nil { + return nil, ErrDatabaseError + } else if mod.ID == 0 { + return nil, ErrTeamNotFound + } else { + return mod, nil + } +} + +func (r team) Find(ctx context.Context, filter *types.TeamFilter) ([]*types.Team, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + var params = make([]interface{}, 0) + sql := "SELECT * FROM teams WHERE " + sqlTeamScope + + if filter != nil { + if filter.Query != "" { + sql += "AND name LIKE ?" + params = append(params, filter.Query+"%") + } + } + + sql += " ORDER BY name ASC" + + rval := make([]*types.Team, 0) + if err := db.Select(&rval, sql, params...); err != nil { + panic(err) + return nil, ErrDatabaseError + } else { + return rval, nil + } +} + +func (r team) Create(ctx context.Context, mod *types.Team) (*types.Team, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod.SetID(factory.Sonyflake.NextID()) + if err := db.Insert("teams", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r team) Update(ctx context.Context, mod *types.Team) (*types.Team, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + if err := db.Replace("teams", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r team) Archive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "teams", "archived_at", time.Now(), id) +} + +func (r team) Unarchive(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "teams", "archived_at", nil, id) +} + +func (r team) Delete(ctx context.Context, id uint64) error { + return simpleDelete(ctx, "teams", id) +} + +func (r team) Merge(ctx context.Context, id, targetTeamId uint64) error { + return ErrNotImplemented +} + +func (r team) Move(ctx context.Context, id, targetOrganisationId uint64) error { + return ErrNotImplemented +} diff --git a/sam/repository/user.go b/sam/repository/user.go new file mode 100644 index 000000000..4e15f2203 --- /dev/null +++ b/sam/repository/user.go @@ -0,0 +1,121 @@ +package repository + +import ( + "context" + "github.com/crusttech/crust/sam/types" + "github.com/titpetric/factory" + "time" +) + +const ( + sqlUserScope = "suspended_at IS NULL AND deleted_at IS NULL" + sqlUserSelect = "SELECT * FROM users WHERE " + sqlUserScope + + ErrUserNotFound = repositoryError("UserNotFound") +) + +type ( + user struct{} +) + +func User() user { + return user{} +} + +func (r user) FindByUsername(ctx context.Context, username string) (*types.User, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.User{} + if err := db.Get(mod, "SELECT * FROM users WHERE username = ? AND "+sqlUserScope, username); err != nil { + return nil, ErrDatabaseError + } else if mod.ID == 0 { + return nil, ErrUserNotFound + } else { + return mod, nil + } +} + +func (r user) FindById(ctx context.Context, id uint64) (*types.User, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod := &types.User{} + if err := db.Get(mod, "SELECT * FROM users WHERE id = ? AND "+sqlUserScope, id); err != nil { + return nil, ErrDatabaseError + } else if mod.ID == 0 { + return nil, ErrUserNotFound + } else { + return mod, nil + } +} + +func (r user) Find(ctx context.Context, filter *types.UserFilter) ([]*types.User, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + var params = make([]interface{}, 0) + sql := "SELECT * FROM users WHERE " + sqlUserScope + + if filter != nil { + if filter.Query != "" { + sql += "AND username LIKE ?" + params = append(params, filter.Query+"%") + } + } + + sql += " ORDER BY username ASC" + + rval := make([]*types.User, 0) + if err := db.Select(&rval, sql, params...); err != nil { + panic(err) + return nil, ErrDatabaseError + } else { + return rval, nil + } +} + +func (r user) Create(ctx context.Context, mod *types.User) (*types.User, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + mod.SetID(factory.Sonyflake.NextID()) + if err := db.Insert("users", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r user) Update(ctx context.Context, mod *types.User) (*types.User, error) { + db, err := factory.Database.Get() + if err != nil { + return nil, ErrDatabaseError + } + + if err := db.Replace("users", mod); err != nil { + return nil, ErrDatabaseError + } else { + return mod, nil + } +} + +func (r user) Suspend(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "users", "suspend_at", time.Now(), id) +} + +func (r user) Unsuspend(ctx context.Context, id uint64) error { + return simpleUpdate(ctx, "users", "suspend_at", nil, id) +} + +func (r user) Delete(ctx context.Context, id uint64) error { + return simpleDelete(ctx, "users", id) +} diff --git a/sam/rest/channel.go b/sam/rest/channel.go index d603dd6f5..49e46924d 100644 --- a/sam/rest/channel.go +++ b/sam/rest/channel.go @@ -3,133 +3,66 @@ package rest import ( "context" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" "github.com/titpetric/factory" "github.com/crusttech/crust/sam/rest/server" + "github.com/crusttech/crust/sam/service" "github.com/crusttech/crust/sam/types" ) var _ = errors.Wrap -const ( - sqlChannelScope = "deleted_at IS NULL AND archived_at IS NULL" - sqlChannelSelect = "SELECT * FROM channels WHERE " + sqlChannelScope -) - -type Channel struct{} - -func (Channel) New() *Channel { - return &Channel{} -} - -func (*Channel) Create(ctx context.Context, r *server.ChannelCreateRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err +type ( + Channel struct { + service channelService } - // @todo: topic message/log entry - // @todo: channel name cmessage/log entry - // @todo: permission check if user can add channel + channelService interface { + FindById(context.Context, uint64) (*types.Channel, error) + Find(context.Context, *types.ChannelFilter) ([]*types.Channel, error) - c := types.Channel{}. + Create(context.Context, *types.Channel) (*types.Channel, error) + Update(context.Context, *types.Channel) (*types.Channel, error) + + deleter + archiver + } +) + +func (Channel) New() *Channel { + return &Channel{service: service.Channel()} +} + +func (ctrl *Channel) Create(ctx context.Context, r *server.ChannelCreateRequest) (interface{}, error) { + channel := types.Channel{}. New(). SetName(r.Name). SetTopic(r.Topic). SetMeta([]byte("{}")). SetID(factory.Sonyflake.NextID()) - return c, db.Insert("channels", c) + return ctrl.service.Create(ctx, channel) } -func (*Channel) Edit(ctx context.Context, r *server.ChannelEditRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - /*var c *types.Channel - if c, err = c.load(r.ID); err != nil { - return nil, err - }*/ - - // @todo: topic change message/log entry - // @todo: channel name change message/log entry - // @todo: permission check if user can edit channel - // @todo: make sure archived & deleted entries can not be edited - // @todo: handle channel moving - // @todo: handle channel archiving - - c := types.Channel{}. +func (ctrl *Channel) Edit(ctx context.Context, r *server.ChannelEditRequest) (interface{}, error) { + channel := types.Channel{}. New(). SetName(r.Name). SetTopic(r.Topic) - return c, db.Replace("channels", c) + return ctrl.service.Update(ctx, channel) } -func (*Channel) Delete(ctx context.Context, r *server.ChannelDeleteRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - /*var c *types.Channel - if c, err = c.load(r.ID); err != nil { - return nil, err - }*/ - - // @todo: make history unavailable - // @todo: notify users that channel has been removed (remove from web UI) - // @todo: permissions check if user cah remove channel - - stmt := "UPDATE channels SET deleted_at = NOW() WHERE id = ? AND deleted_at IS NULL" - spew.Dump(r.ID) - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() +func (ctrl *Channel) Delete(ctx context.Context, r *server.ChannelDeleteRequest) (interface{}, error) { + return nil, ctrl.service.Delete(ctx, r.ID) } -func (s *Channel) Read(ctx context.Context, r *server.ChannelReadRequest) (interface{}, error) { - return s.load(r.ID) +func (ctrl *Channel) Read(ctx context.Context, r *server.ChannelReadRequest) (interface{}, error) { + return ctrl.service.FindById(ctx, r.ID) } -func (*Channel) List(ctx context.Context, r *server.ChannelListRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check to return only channels that user has access to - // @todo: actual searching not just a full select - - res := make([]types.Channel, 0) - err = db.Select(&res, sqlChannelSelect+" ORDER BY name ASC") - return res, err -} - -func (*Channel) load(id uint64) (*types.Channel, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - c := types.Channel{}.New() - - if id == 0 { - return nil, errors.New("Provide channel ID") - } else if err := db.Get(c, sqlChannelSelect+" AND id = ?", id); err != nil { - return nil, err - } else if c.ID != id { - spew.Dump(c) - return nil, errors.New("Unexisting channel") - } - - // @todo: permission check if user can read channel - - return c, nil +func (ctrl *Channel) List(ctx context.Context, r *server.ChannelListRequest) (interface{}, error) { + return ctrl.service.Find(ctx, &types.ChannelFilter{Query: r.Query}) } diff --git a/sam/rest/organisation.go b/sam/rest/organisation.go index 3f4248a1e..e3d574027 100644 --- a/sam/rest/organisation.go +++ b/sam/rest/organisation.go @@ -2,111 +2,63 @@ package rest import ( "context" - "fmt" - - "github.com/pkg/errors" - "github.com/titpetric/factory" - "github.com/crusttech/crust/sam/rest/server" "github.com/crusttech/crust/sam/types" + "github.com/pkg/errors" ) var _ = errors.Wrap -const ( - sqlOrganisationScope = "deleted_at IS NULL AND archived_at IS NULL" - sqlOrganisationSelect = "SELECT * FROM organisations WHERE " + sqlOrganisationScope -) +type ( + Organisation struct { + service organisationService + } -type Organisation struct{} + organisationService interface { + FindById(context.Context, uint64) (*types.Organisation, error) + Find(context.Context, *types.OrganisationFilter) ([]*types.Organisation, error) + + Create(context.Context, *types.Organisation) (*types.Organisation, error) + Update(context.Context, *types.Organisation) (*types.Organisation, error) + + deleter + archiver + } +) func (Organisation) New() *Organisation { return &Organisation{} } -func (*Organisation) Create(ctx context.Context, r *server.OrganisationCreateRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check if user can add/edit organisation - // @todo: make sure archived & deleted entries can not be edited - - o := types.Organisation{}.New().SetName(r.Name).SetID(factory.Sonyflake.NextID()) - return o, db.Insert("organisation", o) +func (ctrl *Organisation) Read(ctx context.Context, r *server.OrganisationReadRequest) (interface{}, error) { + return ctrl.service.FindById(ctx, r.ID) } -func (*Organisation) Edit(ctx context.Context, r *server.OrganisationEditRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check if user can add/edit organisation - // @todo: make sure archived & deleted entries can not be edited - - o := types.Organisation{}.New().SetID(r.ID).SetName(r.Name) - return o, db.Replace("organisation", o) +func (ctrl *Organisation) List(ctx context.Context, r *server.OrganisationListRequest) (interface{}, error) { + return ctrl.service.Find(ctx, &types.OrganisationFilter{Query: r.Query}) } -func (*Organisation) Remove(ctx context.Context, r *server.OrganisationRemoveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } +func (ctrl *Organisation) Create(ctx context.Context, r *server.OrganisationCreateRequest) (interface{}, error) { + org := types.Organisation{}. + New(). + SetName(r.Name) - // @todo: permission check - // @todo: don't actually delete organisation?! - - stmt := "UPDATE organisationss SET deleted_at = NOW() WHERE deleted_at IS NULL AND id = ?" - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() + return ctrl.service.Create(ctx, org) } -func (*Organisation) Read(ctx context.Context, r *server.OrganisationReadRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } +func (ctrl *Organisation) Edit(ctx context.Context, r *server.OrganisationEditRequest) (interface{}, error) { + org := types.Organisation{}. + New(). + SetID(r.ID). + SetName(r.Name) - // @todo: permissions check - - o := types.Organisation{}.New() - return o, db.Get(o, sqlOrganisationSelect+" AND id = ?", r.ID) + return ctrl.service.Update(ctx, org) } -func (*Organisation) List(ctx context.Context, r *server.OrganisationListRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permissions check - // @todo: actual search for org - - res := make([]Organisation, 0) - err = db.Select(&res, sqlOrganisationSelect+" WHERE label LIKE = ? ORDER BY label ASC", r.Query+"%") - return res, err +func (ctrl *Organisation) Remove(ctx context.Context, r *server.OrganisationRemoveRequest) (interface{}, error) { + return nil, ctrl.service.Delete(ctx, r.ID) } -func (*Organisation) Archive(ctx context.Context, r *server.OrganisationArchiveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check - - stmt := fmt.Sprintf( - "UPDATE organisation SET archived_at = NOW() WHERE %s AND id = ?", - sqlChannelScope) - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() +func (ctrl *Organisation) Archive(ctx context.Context, r *server.OrganisationArchiveRequest) (interface{}, error) { + return nil, ctrl.service.Archive(ctx, r.ID) } diff --git a/sam/rest/rest.go b/sam/rest/rest.go new file mode 100644 index 000000000..f306080a8 --- /dev/null +++ b/sam/rest/rest.go @@ -0,0 +1,20 @@ +package rest + +import ( + "context" +) + +type ( + suspender interface { + Suspend(context.Context, uint64) error + Unsuspend(context.Context, uint64) error + } + archiver interface { + Archive(context.Context, uint64) error + Unarchive(context.Context, uint64) error + } + + deleter interface { + Delete(context.Context, uint64) error + } +) diff --git a/sam/rest/team.go b/sam/rest/team.go index 190ceb1fe..6fab4e76e 100644 --- a/sam/rest/team.go +++ b/sam/rest/team.go @@ -2,111 +2,73 @@ package rest import ( "context" - "fmt" - - "github.com/pkg/errors" - "github.com/titpetric/factory" - "github.com/crusttech/crust/sam/rest/server" "github.com/crusttech/crust/sam/types" + "github.com/pkg/errors" ) var _ = errors.Wrap -const ( - sqlTeamScope = "deleted_at IS NULL AND archived_at IS NULL" - sqlTeamSelect = "SELECT * FROM teams WHERE " + sqlTeamScope -) +type ( + Team struct { + service teamService + } -type Team struct{} + teamService interface { + FindById(context.Context, uint64) (*types.Team, error) + Find(context.Context, *types.TeamFilter) ([]*types.Team, error) + + Create(context.Context, *types.Team) (*types.Team, error) + Update(context.Context, *types.Team) (*types.Team, error) + Merge(context.Context, *types.Team) error + Move(context.Context, *types.Team) error + + deleter + archiver + } +) func (Team) New() *Team { return &Team{} } -func (*Team) Create(ctx context.Context, r *server.TeamCreateRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check if user can add/edit the team - // @todo: make sure archived & deleted entries can not be edited - - t := types.Team{}.New() - t.SetName(r.Name).SetMemberIDs(r.Members).SetID(factory.Sonyflake.NextID()) - return t, db.Insert("team", t) +func (ctrl *Team) Read(ctx context.Context, r *server.TeamReadRequest) (interface{}, error) { + return ctrl.service.FindById(ctx, r.ID) } -func (*Team) Edit(ctx context.Context, r *server.TeamEditRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - // @todo: permission check if user can add/edit the team - // @todo: make sure archived & deleted entries can not be edited - - t := types.Team{}.New() - t.SetID(r.ID).SetName(r.Name).SetMemberIDs(r.Members) - return t, db.Replace("team", t) +func (ctrl *Team) List(ctx context.Context, r *server.TeamListRequest) (interface{}, error) { + return ctrl.service.Find(ctx, &types.TeamFilter{Query: r.Query}) } -func (*Team) Remove(ctx context.Context, r *server.TeamRemoveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } +func (ctrl *Team) Create(ctx context.Context, r *server.TeamCreateRequest) (interface{}, error) { + org := types.Team{}. + New(). + SetName(r.Name) - stmt := "UPDATE teams SET deleted_at = NOW() WHERE deleted_at IS NULL AND id = ?" - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() + return ctrl.service.Create(ctx, org) } -func (*Team) Read(ctx context.Context, r *server.TeamReadRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } +func (ctrl *Team) Edit(ctx context.Context, r *server.TeamEditRequest) (interface{}, error) { + org := types.Team{}. + New(). + SetID(r.ID). + SetName(r.Name) - t := types.Team{}.New() - return t, db.Get(t, sqlTeamSelect+" AND id = ?", r.ID) + return ctrl.service.Update(ctx, org) } -func (*Team) List(ctx context.Context, r *server.TeamListRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - res := make([]Team, 0) - err = db.Select(&res, sqlTeamSelect+" ORDER BY name ASC") - return res, err +func (ctrl *Team) Remove(ctx context.Context, r *server.TeamRemoveRequest) (interface{}, error) { + return nil, ctrl.service.Delete(ctx, r.ID) } -func (*Team) Archive(ctx context.Context, r *server.TeamArchiveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - stmt := fmt.Sprintf( - "UPDATE teams SET archived_at = NOW() WHERE %s AND id = ?", - sqlTeamScope) - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() +func (ctrl *Team) Archive(ctx context.Context, r *server.TeamArchiveRequest) (interface{}, error) { + return nil, ctrl.service.Archive(ctx, r.ID) } -func (*Team) Move(ctx context.Context, r *server.TeamMoveRequest) (interface{}, error) { - return nil, errors.New("Not implemented: Team.move") +func (ctrl *Team) Merge(ctx context.Context, r *server.TeamMergeRequest) (interface{}, error) { + return nil, ctrl.service.Merge(ctx, &types.Team{ID: r.ID}) } -func (*Team) Merge(ctx context.Context, r *server.TeamMergeRequest) (interface{}, error) { - return nil, errors.New("Not implemented: Team.merge") +func (ctrl *Team) Move(ctx context.Context, r *server.TeamMoveRequest) (interface{}, error) { + return nil, ctrl.service.Move(ctx, &types.Team{ID: r.ID}) } diff --git a/sam/rest/user.go b/sam/rest/user.go index 4d24d18e9..b599a0f87 100644 --- a/sam/rest/user.go +++ b/sam/rest/user.go @@ -3,118 +3,43 @@ package rest import ( "context" - "github.com/pkg/errors" - "github.com/titpetric/factory" - "github.com/crusttech/crust/sam/rest/server" "github.com/crusttech/crust/sam/service" "github.com/crusttech/crust/sam/types" + "github.com/pkg/errors" ) var _ = errors.Wrap -const ( - sqlUserScope = "suspended_at IS NULL AND archived_at IS NULL" - sqlUserSelect = "SELECT * FROM users WHERE " + sqlUserScope -) - type ( User struct { - service UserInterface + service userService } - UserInterface interface { - Set(*types.User) - CanLogin() bool - GeneratePassword(value string) ([]byte, error) - ValidatePassword(value string) bool - ValidateUser() bool + userService interface { + ValidateCredentials(context.Context, string, string) (*types.User, error) + + FindById(context.Context, uint64) (*types.User, error) + Find(context.Context, *types.UserFilter) ([]*types.User, error) + + Create(context.Context, *types.User) (*types.User, error) + Update(context.Context, *types.User) (*types.User, error) + + deleter + suspender } ) func (User) New() *User { - return &User{service.User{}.New()} + return &User{service: service.User()} } -/* -func (self *User) Read(ctx context.Context, r *teamReadRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - t := types.User{}.New() - return t, db.Get(t, sqlUserSelect+" AND id = ?", r.ID) -} -*/ - // User lookup & login func (self *User) Login(ctx context.Context, r *server.UserLoginRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - u := types.User{}.New() - if err = db.Get(u, sqlUserSelect+" AND username = ?", r.Username); err != nil { - return nil, err - } - self.service.Set(u) - - if self.service.ValidateUser() || !self.service.ValidatePassword(r.Password) { - return nil, errors.New("Invalid username and password combination") - } - - if !self.service.CanLogin() { - return nil, errors.New("User is not allowed to login") - } - - return u, nil + return self.service.ValidateCredentials(ctx, r.Username, r.Password) } // Searches the users table in the database to find users by matching (by-prefix) their.Username -func (*User) Search(ctx context.Context, r *server.UserSearchRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - uu := []types.User{} - if err != db.Get(uu, sqlUserSelect+" AND username LIKE ?", r.Query+"%") { - return nil, err - } - - return uu, nil +func (self *User) Search(ctx context.Context, r *server.UserSearchRequest) (interface{}, error) { + return self.service.Find(ctx, &types.UserFilter{Query: r.Query}) } - -/* -func (self *User) Remove(ctx context.Context, r *teamRemoveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - stmt := "UPDATE users SET deleted_at = NOW() WHERE deleted_at IS NULL AND id = ?" - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() -} - -func (self *User) Archive(ctx context.Context, r *teamArchiveRequest) (interface{}, error) { - db, err := factory.Database.Get() - if err != nil { - return nil, err - } - - stmt := fmt.Sprintf( - "UPDATE users SET archived_at = NOW() WHERE %s AND id = ?", - sqlUserScope) - - return nil, func() error { - _, err := db.Exec(stmt, r.ID) - return err - }() -} -*/ diff --git a/sam/service/channel.go b/sam/service/channel.go new file mode 100644 index 000000000..6f7979e35 --- /dev/null +++ b/sam/service/channel.go @@ -0,0 +1,79 @@ +package service + +import ( + "context" + "github.com/crusttech/crust/sam/repository" + "github.com/crusttech/crust/sam/types" +) + +type ( + channel struct { + repository channelRepository + } + + channelRepository interface { + FindById(context.Context, uint64) (*types.Channel, error) + Find(context.Context, *types.ChannelFilter) ([]*types.Channel, error) + + Create(context.Context, *types.Channel) (*types.Channel, error) + Update(context.Context, *types.Channel) (*types.Channel, error) + + deleter + archiver + } +) + +func Channel() *channel { + return &channel{repository: repository.Channel()} +} + +func (svc channel) FindById(ctx context.Context, id uint64) (*types.Channel, error) { + // @todo: permission check if current user can read channel + return svc.repository.FindById(ctx, id) +} + +func (svc channel) Find(ctx context.Context, filter *types.ChannelFilter) ([]*types.Channel, error) { + // @todo: permission check to return only channels that channel has access to + // @todo: actual searching not just a full select + return svc.repository.Find(ctx, filter) +} + +func (svc channel) Create(ctx context.Context, mod *types.Channel) (*types.Channel, error) { + // @todo: topic message/log entry + // @todo: channel name cmessage/log entry + // @todo: permission check if channel can add channel + + return svc.repository.Create(ctx, mod) +} + +func (svc channel) Update(ctx context.Context, mod *types.Channel) (*types.Channel, error) { + // @todo: topic change message/log entry + // @todo: channel name change message/log entry + // @todo: permission check if current user can edit channel + // @todo: make sure archived & deleted entries can not be edited + // @todo: handle channel movinga + // @todo: handle channel archiving + + return svc.repository.Update(ctx, mod) +} + +func (svc channel) Delete(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that channel has been removed (remove from web UI) + // @todo: permissions check if current user can remove channel + return svc.repository.Delete(ctx, id) +} + +func (svc channel) Archive(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that channel has been removed (remove from web UI) + // @todo: permissions check if current user can remove channel + return svc.repository.Archive(ctx, id) +} + +func (svc channel) Unarchive(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that channel has been removed (remove from web UI) + // @todo: permissions check if current user can remove channel + return svc.repository.Unarchive(ctx, id) +} diff --git a/sam/service/error.go b/sam/service/error.go new file mode 100644 index 000000000..f2180634b --- /dev/null +++ b/sam/service/error.go @@ -0,0 +1,9 @@ +package service + +type ( + serviceError string +) + +func (e serviceError) Error() string { + return "crust.sam.service." + string(e) +} diff --git a/sam/service/organisation.go b/sam/service/organisation.go new file mode 100644 index 000000000..91cc32569 --- /dev/null +++ b/sam/service/organisation.go @@ -0,0 +1,74 @@ +package service + +import ( + "context" + "github.com/crusttech/crust/sam/repository" + "github.com/crusttech/crust/sam/types" +) + +type ( + organisation struct { + repository organisationRepository + } + + organisationRepository interface { + FindById(context.Context, uint64) (*types.Organisation, error) + Find(context.Context, *types.OrganisationFilter) ([]*types.Organisation, error) + + Create(context.Context, *types.Organisation) (*types.Organisation, error) + Update(context.Context, *types.Organisation) (*types.Organisation, error) + + deleter + archiver + } +) + +func Organisation() *organisation { + return &organisation{repository: repository.Organisation()} +} + +func (svc organisation) FindById(ctx context.Context, id uint64) (*types.Organisation, error) { + // @todo: permission check if current user can read organisation + return svc.repository.FindById(ctx, id) +} + +func (svc organisation) Find(ctx context.Context, filter *types.OrganisationFilter) ([]*types.Organisation, error) { + // @todo: permission check to return only organisations that organisation has access to + // @todo: actual searching not just a full select + return svc.repository.Find(ctx, filter) +} + +func (svc organisation) Create(ctx context.Context, mod *types.Organisation) (*types.Organisation, error) { + // @todo: permission check if current user can add/edit organisation + // @todo: make sure archived & deleted entries can not be edited + + return svc.repository.Create(ctx, mod) +} + +func (svc organisation) Update(ctx context.Context, mod *types.Organisation) (*types.Organisation, error) { + // @todo: permission check if current user can add/edit organisation + // @todo: make sure archived & deleted entries can not be edited + + return svc.repository.Update(ctx, mod) +} + +func (svc organisation) Delete(ctx context.Context, id uint64) error { + // @todo: permissions check if current user can remove organisation + // @todo: make history unavailable + // @todo: notify users that organisation has been removed (remove from web UI) + return svc.repository.Delete(ctx, id) +} + +func (svc organisation) Archive(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that organisation has been removed (remove from web UI) + // @todo: permissions check if current user can archive organisation + return svc.repository.Archive(ctx, id) +} + +func (svc organisation) Unarchive(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that organisation has been removed (remove from web UI) + // @todo: permissions check if current user can unarchive organisation + return svc.repository.Unarchive(ctx, id) +} diff --git a/sam/service/service.go b/sam/service/service.go new file mode 100644 index 000000000..75831b561 --- /dev/null +++ b/sam/service/service.go @@ -0,0 +1,20 @@ +package service + +import ( + "context" +) + +type ( + suspender interface { + Suspend(context.Context, uint64) error + Unsuspend(context.Context, uint64) error + } + archiver interface { + Archive(context.Context, uint64) error + Unarchive(context.Context, uint64) error + } + + deleter interface { + Delete(context.Context, uint64) error + } +) diff --git a/sam/service/team.go b/sam/service/team.go new file mode 100644 index 000000000..00386716f --- /dev/null +++ b/sam/service/team.go @@ -0,0 +1,85 @@ +package service + +import ( + "context" + "github.com/crusttech/crust/sam/repository" + "github.com/crusttech/crust/sam/types" +) + +type ( + team struct { + repository teamRepository + } + + teamRepository interface { + FindById(context.Context, uint64) (*types.Team, error) + Find(context.Context, *types.TeamFilter) ([]*types.Team, error) + + Create(context.Context, *types.Team) (*types.Team, error) + Update(context.Context, *types.Team) (*types.Team, error) + + Merge(context.Context, uint64, uint64) error + Move(context.Context, uint64, uint64) error + + deleter + archiver + } +) + +func Team() *team { + return &team{repository: repository.Team()} +} + +func (svc team) FindById(ctx context.Context, id uint64) (*types.Team, error) { + // @todo: permission check if current user has access to this team + return svc.repository.FindById(ctx, id) +} + +func (svc team) Find(ctx context.Context, filter *types.TeamFilter) ([]*types.Team, error) { + // @todo: permission check to return only teams that current user has access to + return svc.repository.Find(ctx, filter) +} + +func (svc team) Create(ctx context.Context, mod *types.Team) (*types.Team, error) { + // @todo: permission check if current user can add/edit team + + return svc.repository.Create(ctx, mod) +} + +func (svc team) Update(ctx context.Context, mod *types.Team) (*types.Team, error) { + // @todo: permission check if current user can add/edit team + // @todo: make sure archived & deleted entries can not be edited + + return svc.repository.Update(ctx, mod) +} + +func (svc team) Delete(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that team has been removed (remove from web UI) + // @todo: permissions check if current user can remove team + return svc.repository.Delete(ctx, id) +} + +func (svc team) Archive(ctx context.Context, id uint64) error { + // @todo: make history unavailable + // @todo: notify users that team has been removed (remove from web UI) + // @todo: permissions check if current user can remove team + return svc.repository.Archive(ctx, id) +} + +func (svc team) Unarchive(ctx context.Context, id uint64) error { + // @todo: permissions check if current user can unarchive team + // @todo: make history accessible + // @todo: notify users that team has been unarchived + return svc.repository.Unarchive(ctx, id) +} + +func (svc team) Merge(ctx context.Context, id uint64, targetTeamId uint64) error { + // @todo: permission check if current user can merge team + return svc.repository.Merge(ctx, id, targetTeamId) +} + +func (svc team) Move(ctx context.Context, id uint64, targetOrganisationId uint64) error { + // @todo: permission check if current user can move team to another organisation + return svc.repository.Move(ctx, id, targetOrganisationId) +} diff --git a/sam/service/user.go b/sam/service/user.go index d5cd2f188..0a90fa972 100644 --- a/sam/service/user.go +++ b/sam/service/user.go @@ -1,35 +1,105 @@ package service import ( - "golang.org/x/crypto/bcrypt" - + "context" + "github.com/crusttech/crust/sam/repository" "github.com/crusttech/crust/sam/types" + "golang.org/x/crypto/bcrypt" ) -type User struct { - *types.User +const ( + ErrUserInvalidCredentials = serviceError("UserInvalidCredentials") + ErrUserLocked = serviceError("UserLocked") +) + +type ( + user struct { + repository userRepository + } + + userRepository interface { + FindByUsername(context.Context, string) (*types.User, error) + FindById(context.Context, uint64) (*types.User, error) + Find(context.Context, *types.UserFilter) ([]*types.User, error) + + Create(context.Context, *types.User) (*types.User, error) + Update(context.Context, *types.User) (*types.User, error) + + deleter + suspender + } +) + +func User() *user { + return &user{repository: repository.User()} } -func (User) New() *User { - return &User{types.User{}.New()} +func (svc user) ValidateCredentials(ctx context.Context, username, password string) (*types.User, error) { + user, err := svc.repository.FindByUsername(ctx, username) + if err != nil { + return nil, err + } + + if !svc.validatePassword(user, password) { + return nil, ErrUserInvalidCredentials + } + + if !svc.canLogin(user) { + return nil, ErrUserLocked + } + + return user, nil } -func (u *User) Set(user *types.User) { - u.User = user +func (svc user) FindById(ctx context.Context, id uint64) (*types.User, error) { + return svc.repository.FindById(ctx, id) } -func (u *User) CanLogin() bool { - return u.User != nil && u.User.ID > 0 +func (svc user) Find(ctx context.Context, filter *types.UserFilter) ([]*types.User, error) { + return svc.repository.Find(ctx, filter) } -func (u *User) GeneratePassword(value string) ([]byte, error) { - return bcrypt.GenerateFromPassword([]byte(value), bcrypt.DefaultCost) +func (svc user) Create(ctx context.Context, mod *types.User) (*types.User, error) { + return svc.repository.Create(ctx, mod) } -func (u *User) ValidatePassword(value string) bool { - return u.User != nil && bcrypt.CompareHashAndPassword(u.User.Password, []byte(value)) == nil +func (svc user) Update(ctx context.Context, mod *types.User) (*types.User, error) { + return svc.repository.Update(ctx, mod) } -func (u *User) ValidateUser() bool { - return u.User != nil && u.User.ID > 0 +func (svc user) validatePassword(user *types.User, password string) bool { + return user != nil && + bcrypt.CompareHashAndPassword(user.Password, []byte(password)) == nil +} + +func (svc user) generatePassword(user *types.User, password string) error { + pwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + + user.SetPassword(pwd) + return nil +} + +func (svc user) canLogin(u *types.User) bool { + return u != nil && u.ID > 0 && u.SuspendedAt == nil && u.DeletedAt == nil +} + +func (svc user) Delete(ctx context.Context, id uint64) error { + // @todo: permissions check if current user can delete this user + // @todo: notify users that user has been removed (remove from web UI) + return svc.repository.Delete(ctx, id) +} + +func (svc user) Suspend(ctx context.Context, id uint64) error { + // @todo: permissions check if current user can suspend this user + // @todo: notify users that user has been supsended (remove from web UI) + return svc.repository.Suspend(ctx, id) +} + +func (svc user) Unsuspend(ctx context.Context, id uint64) error { + // @todo: permissions check if current user can unsuspend this user + // @todo: notify users that user has been unsuspended + return svc.repository.Unsuspend(ctx, id) } diff --git a/sam/types/channel_filter.go b/sam/types/channel_filter.go new file mode 100644 index 000000000..6b3b977ea --- /dev/null +++ b/sam/types/channel_filter.go @@ -0,0 +1,7 @@ +package types + +type ( + ChannelFilter struct { + Query string + } +) diff --git a/sam/types/organisation_filter.go b/sam/types/organisation_filter.go new file mode 100644 index 000000000..126eab30e --- /dev/null +++ b/sam/types/organisation_filter.go @@ -0,0 +1,7 @@ +package types + +type ( + OrganisationFilter struct { + Query string + } +) diff --git a/sam/types/team_filter.go b/sam/types/team_filter.go new file mode 100644 index 000000000..80266513a --- /dev/null +++ b/sam/types/team_filter.go @@ -0,0 +1,7 @@ +package types + +type ( + TeamFilter struct { + Query string + } +) diff --git a/sam/types/user.go b/sam/types/user.go index d1275350a..da85371eb 100644 --- a/sam/types/user.go +++ b/sam/types/user.go @@ -22,11 +22,13 @@ import ( type ( // Users User struct { - ID uint64 `db:"id"` - Username string `db:"username"` - Password []byte `json:"-" db:"password"` - SuspendedAt *time.Time `json:",omitempty" db:"suspended_at"` - DeletedAt *time.Time `json:",omitempty" db:"deleted_at"` + ID uint64 `db:"id"` + Username string `db:"username"` + Meta interface{} `json:"-" db:"meta"` + OrganisationID uint64 `db:"rel_organisation"` + Password []byte `json:"-" db:"password"` + SuspendedAt *time.Time `json:",omitempty" db:"suspended_at"` + DeletedAt *time.Time `json:",omitempty" db:"deleted_at"` changed []string } @@ -60,6 +62,28 @@ func (u *User) SetUsername(value string) *User { } return u } +func (u *User) GetMeta() interface{} { + return u.Meta +} + +func (u *User) SetMeta(value interface{}) *User { + if u.Meta != value { + u.changed = append(u.changed, "Meta") + u.Meta = value + } + return u +} +func (u *User) GetOrganisationID() uint64 { + return u.OrganisationID +} + +func (u *User) SetOrganisationID(value uint64) *User { + if u.OrganisationID != value { + u.changed = append(u.changed, "OrganisationID") + u.OrganisationID = value + } + return u +} func (u *User) GetPassword() []byte { return u.Password } diff --git a/sam/types/user_filter.go b/sam/types/user_filter.go new file mode 100644 index 000000000..3aa58b292 --- /dev/null +++ b/sam/types/user_filter.go @@ -0,0 +1,7 @@ +package types + +type ( + UserFilter struct { + Query string + } +)