Enregistrement et affichage d'un flux d'évènements

- Ajout d'une nouvelle entité "Event"
- Affichage d'une "timeline" sur le tableau de bord
- Création semi-automatique des évènements lors des modifications par
  les utilisateurs
This commit is contained in:
2020-10-02 16:37:24 +02:00
parent 61eacefd6c
commit f169169bc7
27 changed files with 692 additions and 98 deletions

View File

@ -2,7 +2,7 @@ package graph
import (
"context"
"encoding/json"
"reflect"
"forge.cadoles.com/Cadoles/daddy/internal/orm"
"gitlab.com/wpetit/goweb/middleware/container"
@ -12,6 +12,11 @@ import (
)
func handleCreateDecisionSupportFile(ctx context.Context, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
user, db, err := getSessionUser(ctx)
if err != nil {
return nil, errs.WithStack(err)
}
authorized, err := isAuthorized(ctx, &model.DecisionSupportFile{}, model.ActionCreate)
if err != nil {
return nil, errs.WithStack(err)
@ -21,9 +26,6 @@ func handleCreateDecisionSupportFile(ctx context.Context, changes *model.Decisio
return nil, errs.WithStack(ErrForbidden)
}
ctn := container.Must(ctx)
db := orm.Must(ctn).DB()
repo := model.NewDSFRepository(db)
dsf, err := repo.Create(ctx, changes)
@ -31,21 +33,29 @@ func handleCreateDecisionSupportFile(ctx context.Context, changes *model.Decisio
return nil, errs.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeCreated, dsf); err != nil {
return nil, errs.WithStack(err)
}
return dsf, nil
}
func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
ctn := container.Must(ctx)
db := orm.Must(ctn).DB()
repo := model.NewDSFRepository(db)
dsf, err := repo.Find(ctx, id)
user, db, err := getSessionUser(ctx)
if err != nil {
return nil, errs.WithStack(err)
}
authorized, err := isAuthorized(ctx, dsf, model.ActionUpdate)
repo := model.NewDSFRepository(db)
prevDsf, err := repo.Find(ctx, id)
if err != nil {
return nil, errs.WithStack(err)
}
authorized, err := isAuthorized(ctx, prevDsf, model.ActionUpdate)
if err != nil {
return nil, errs.WithStack(err)
}
@ -54,11 +64,31 @@ func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *mo
return nil, errs.WithStack(ErrForbidden)
}
dsf, err = repo.Update(ctx, id, changes)
dsf, err := repo.Update(ctx, id, changes)
if err != nil {
return nil, errs.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if changes != nil && changes.Status != nil && prevDsf.Status != *changes.Status {
if _, err := eventRepo.Add(ctx, user, model.EventTypeStatusChanged, dsf); err != nil {
return nil, errs.WithStack(err)
}
}
if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title {
if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil {
return nil, errs.WithStack(err)
}
}
if changes != nil && !reflect.DeepEqual(prevDsf.Sections, dsf.Sections) {
if _, err := eventRepo.Add(ctx, user, model.EventTypeUpdated, dsf); err != nil {
return nil, errs.WithStack(err)
}
}
return dsf, nil
}
@ -88,13 +118,3 @@ func handleDecisionSupportFiles(ctx context.Context, filter *model.DecisionSuppo
return dsfs, nil
}
func handleSections(ctx context.Context, dsf *model.DecisionSupportFile) (map[string]interface{}, error) {
sections := make(map[string]interface{})
if err := json.Unmarshal(dsf.Sections.RawMessage, &sections); err != nil {
return nil, errs.WithStack(err)
}
return sections, nil
}

View File

@ -0,0 +1,24 @@
package graph
import (
"context"
"forge.cadoles.com/Cadoles/daddy/internal/model"
"forge.cadoles.com/Cadoles/daddy/internal/orm"
errs "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
)
func handleEvents(ctx context.Context, filter *model.EventFilter) ([]*model.Event, error) {
ctn := container.Must(ctx)
db := orm.Must(ctn).DB()
repo := model.NewEventRepository(db)
events, err := repo.Search(ctx, filter)
if err != nil {
return nil, errs.WithStack(err)
}
return events, nil
}

View File

@ -18,6 +18,16 @@ type Workgroup {
members: [User]!
}
type Event {
id: ID!
type: String!
createdAt: Time!
updatedAt: Time!
objectId: ID!
objectType: String!
user: User!
}
input WorkgroupsFilter {
ids: [ID]
}
@ -44,9 +54,19 @@ input AuthorizationObject {
decisionSupportFileId: ID
}
input EventFilter {
objectType: String
objectId: ID
userId: ID
type: String
from: Time
to: Time
}
type Query {
userProfile: User
workgroups(filter: WorkgroupsFilter): [Workgroup]!
decisionSupportFiles(filter: DecisionSupportFileFilter): [DecisionSupportFile]!
events(filter: EventFilter): [Event]!
isAuthorized(action: String!, object: AuthorizationObject!): Boolean!
}

View File

@ -15,8 +15,16 @@ func (r *decisionSupportFileResolver) ID(ctx context.Context, obj *model1.Decisi
return strconv.FormatUint(uint64(obj.ID), 10), nil
}
func (r *decisionSupportFileResolver) Sections(ctx context.Context, obj *model1.DecisionSupportFile) (map[string]interface{}, error) {
return handleSections(ctx, obj)
func (r *eventResolver) ID(ctx context.Context, obj *model1.Event) (string, error) {
return strconv.FormatUint(uint64(obj.ID), 10), nil
}
func (r *eventResolver) Type(ctx context.Context, obj *model1.Event) (string, error) {
return string(obj.Type), nil
}
func (r *eventResolver) ObjectID(ctx context.Context, obj *model1.Event) (string, error) {
return strconv.FormatUint(uint64(obj.ObjectID), 10), nil
}
func (r *queryResolver) UserProfile(ctx context.Context) (*model1.User, error) {
@ -31,6 +39,10 @@ func (r *queryResolver) DecisionSupportFiles(ctx context.Context, filter *model1
return handleDecisionSupportFiles(ctx, filter)
}
func (r *queryResolver) Events(ctx context.Context, filter *model1.EventFilter) ([]*model1.Event, error) {
return handleEvents(ctx, filter)
}
func (r *queryResolver) IsAuthorized(ctx context.Context, action string, object model1.AuthorizationObject) (bool, error) {
return handleIsAuthorized(ctx, action, object)
}
@ -48,6 +60,9 @@ func (r *Resolver) DecisionSupportFile() generated.DecisionSupportFileResolver {
return &decisionSupportFileResolver{r}
}
// Event returns generated.EventResolver implementation.
func (r *Resolver) Event() generated.EventResolver { return &eventResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
@ -58,6 +73,17 @@ func (r *Resolver) User() generated.UserResolver { return &userResolver{r} }
func (r *Resolver) Workgroup() generated.WorkgroupResolver { return &workgroupResolver{r} }
type decisionSupportFileResolver struct{ *Resolver }
type eventResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type userResolver struct{ *Resolver }
type workgroupResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *decisionSupportFileResolver) Sections(ctx context.Context, obj *model1.DecisionSupportFile) (map[string]interface{}, error) {
return obj.Sections, nil
}

View File

@ -72,6 +72,12 @@ func handleJoinWorkgroup(ctx context.Context, rawWorkgroupID string) (*model.Wor
return nil, errors.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeJoined, workgroup); err != nil {
return nil, errors.WithStack(err)
}
return workgroup, nil
}
@ -102,6 +108,12 @@ func handleLeaveWorkgroup(ctx context.Context, workgroupID string) (*model.Workg
return nil, errors.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeLeaved, workgroup); err != nil {
return nil, errors.WithStack(err)
}
return workgroup, nil
}
@ -115,7 +127,7 @@ func handleCreateWorkgroup(ctx context.Context, changes model.WorkgroupChanges)
return nil, errs.WithStack(ErrForbidden)
}
db, err := getDB(ctx)
user, db, err := getSessionUser(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
@ -127,11 +139,17 @@ func handleCreateWorkgroup(ctx context.Context, changes model.WorkgroupChanges)
return nil, errors.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeCreated, workgroup); err != nil {
return nil, errors.WithStack(err)
}
return workgroup, nil
}
func handleCloseWorkgroup(ctx context.Context, workgroupID string) (*model.Workgroup, error) {
db, err := getDB(ctx)
user, db, err := getSessionUser(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
@ -157,11 +175,17 @@ func handleCloseWorkgroup(ctx context.Context, workgroupID string) (*model.Workg
return nil, errors.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeClosed, workgroup); err != nil {
return nil, errors.WithStack(err)
}
return workgroup, nil
}
func handleUpdateWorkgroup(ctx context.Context, workgroupID string, changes model.WorkgroupChanges) (*model.Workgroup, error) {
db, err := getDB(ctx)
user, db, err := getSessionUser(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
@ -187,5 +211,11 @@ func handleUpdateWorkgroup(ctx context.Context, workgroupID string, changes mode
return nil, errors.WithStack(err)
}
eventRepo := model.NewEventRepository(db)
if _, err := eventRepo.Add(ctx, user, model.EventTypeUpdated, workgroup); err != nil {
return nil, errors.WithStack(err)
}
return workgroup, nil
}