refactor(events): use a concrete doer on project and team events
ProjectUpdated/Deleted, ProjectSharedWith* and TeamCreated/Deleted carried an interface-typed Doer that could not be unmarshaled, forcing the audit registrations to decode anonymous mirror structs. Hydrate the doer via GetUserOrLinkShareUser at the dispatch sites like the task events already do, register the events directly and drop the untyped audit registration path. Webhook payloads for these events now serialize link share doers as their pseudo-user (negative id) instead of the raw link share object, consistent with task events.
This commit is contained in:
parent
f33cde82e2
commit
b3bcab1f72
|
|
@ -45,27 +45,15 @@ func RegisterEventForAudit[T any, PT interface {
|
|||
events.Event
|
||||
}](toEntry func(PT) *Entry) {
|
||||
name := PT(new(T)).Name()
|
||||
RegisterEventNameForAudit(name, func(payload []byte) (*Entry, error) {
|
||||
e := PT(new(T)) // fresh instance per message — handlers run concurrently
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toEntry(e), nil
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterEventNameForAudit is the untyped variant for events which cannot be
|
||||
// unmarshaled into their Go struct directly (e.g. interface-typed Doer
|
||||
// fields); the mapping decodes the raw payload itself.
|
||||
func RegisterEventNameForAudit(name string, toEntry func(payload []byte) (*Entry, error)) {
|
||||
events.RegisterListener(name, &auditListener{handle: func(msg *message.Message) error {
|
||||
if !license.IsFeatureEnabled(license.FeatureAuditLogs) {
|
||||
return nil // license is runtime-mutable — checked per event, not at registration
|
||||
}
|
||||
entry, err := toEntry(msg.Payload)
|
||||
if err != nil {
|
||||
e := PT(new(T)) // fresh instance per message — handlers run concurrently
|
||||
if err := json.Unmarshal(msg.Payload, e); err != nil {
|
||||
return err
|
||||
}
|
||||
entry := toEntry(e)
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package models
|
|||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
)
|
||||
|
||||
/////////////////
|
||||
|
|
@ -230,8 +229,8 @@ func (l *ProjectCreatedEvent) Name() string {
|
|||
|
||||
// ProjectUpdatedEvent represents an event where a project has been updated
|
||||
type ProjectUpdatedEvent struct {
|
||||
Project *Project `json:"project"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Project *Project `json:"project"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for ProjectUpdatedEvent
|
||||
|
|
@ -241,8 +240,8 @@ func (p *ProjectUpdatedEvent) Name() string {
|
|||
|
||||
// ProjectDeletedEvent represents an event where a project has been deleted
|
||||
type ProjectDeletedEvent struct {
|
||||
Project *Project `json:"project"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Project *Project `json:"project"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for ProjectDeletedEvent
|
||||
|
|
@ -258,7 +257,7 @@ func (p *ProjectDeletedEvent) Name() string {
|
|||
type ProjectSharedWithUserEvent struct {
|
||||
Project *Project `json:"project"`
|
||||
User *user.User `json:"user"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for ProjectSharedWithUserEvent
|
||||
|
|
@ -268,9 +267,9 @@ func (p *ProjectSharedWithUserEvent) Name() string {
|
|||
|
||||
// ProjectSharedWithTeamEvent represents an event where a project has been shared with a team
|
||||
type ProjectSharedWithTeamEvent struct {
|
||||
Project *Project `json:"project"`
|
||||
Team *Team `json:"team"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Project *Project `json:"project"`
|
||||
Team *Team `json:"team"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for ProjectSharedWithTeamEvent
|
||||
|
|
@ -308,8 +307,8 @@ func (t *TeamMemberRemovedEvent) Name() string {
|
|||
|
||||
// TeamCreatedEvent represents a TeamCreatedEvent event
|
||||
type TeamCreatedEvent struct {
|
||||
Team *Team `json:"team"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Team *Team `json:"team"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for TeamCreatedEvent
|
||||
|
|
@ -319,8 +318,8 @@ func (t *TeamCreatedEvent) Name() string {
|
|||
|
||||
// TeamDeletedEvent represents a TeamDeletedEvent event
|
||||
type TeamDeletedEvent struct {
|
||||
Team *Team `json:"team"`
|
||||
Doer web.Auth `json:"doer"`
|
||||
Team *Team `json:"team"`
|
||||
Doer *user.User `json:"doer"`
|
||||
}
|
||||
|
||||
// Name defines the name for TeamDeletedEvent
|
||||
|
|
|
|||
|
|
@ -88,23 +88,6 @@ func RegisterListeners() {
|
|||
}
|
||||
}
|
||||
|
||||
// auditDoerRef decodes the doer of events whose Doer field is an interface
|
||||
// and thus can't be unmarshaled into the event struct directly.
|
||||
type auditDoerRef struct {
|
||||
ID int64 `json:"id"`
|
||||
Hash string `json:"hash"` // only set when the doer is a link share
|
||||
}
|
||||
|
||||
func auditActorFromDoerRef(d *auditDoerRef) audit.Actor {
|
||||
if d == nil {
|
||||
return audit.SystemActor()
|
||||
}
|
||||
if d.Hash != "" {
|
||||
return audit.LinkShareActor(d.ID)
|
||||
}
|
||||
return audit.ActorFromDoerID(d.ID)
|
||||
}
|
||||
|
||||
func auditActorFromUser(u *user.User) audit.Actor {
|
||||
if u == nil {
|
||||
return audit.SystemActor()
|
||||
|
|
@ -281,95 +264,51 @@ func registerEventsForAuditLogging() {
|
|||
Target: audit.ProjectTarget(e.Project.ID),
|
||||
}
|
||||
})
|
||||
audit.RegisterEventNameForAudit((&ProjectUpdatedEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Project *Project `json:"project"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
audit.RegisterEventForAudit(func(e *ProjectUpdatedEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionProjectUpdated,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.ProjectTarget(e.Project.ID),
|
||||
}, nil
|
||||
})
|
||||
audit.RegisterEventNameForAudit((&ProjectDeletedEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Project *Project `json:"project"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
})
|
||||
audit.RegisterEventForAudit(func(e *ProjectDeletedEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionProjectDeleted,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.ProjectTarget(e.Project.ID),
|
||||
}, nil
|
||||
})
|
||||
audit.RegisterEventNameForAudit((&ProjectSharedWithUserEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Project *Project `json:"project"`
|
||||
User *user.User `json:"user"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
})
|
||||
audit.RegisterEventForAudit(func(e *ProjectSharedWithUserEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionProjectSharedWithUser,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.ProjectTarget(e.Project.ID),
|
||||
Metadata: map[string]any{"user_id": e.User.ID},
|
||||
}, nil
|
||||
})
|
||||
audit.RegisterEventNameForAudit((&ProjectSharedWithTeamEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Project *Project `json:"project"`
|
||||
Team *Team `json:"team"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
})
|
||||
audit.RegisterEventForAudit(func(e *ProjectSharedWithTeamEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionProjectSharedWithTeam,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.ProjectTarget(e.Project.ID),
|
||||
Metadata: map[string]any{"team_id": e.Team.ID},
|
||||
}, nil
|
||||
}
|
||||
})
|
||||
|
||||
// Teams
|
||||
audit.RegisterEventNameForAudit((&TeamCreatedEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Team *Team `json:"team"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
audit.RegisterEventForAudit(func(e *TeamCreatedEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionTeamCreated,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.TeamTarget(e.Team.ID),
|
||||
}, nil
|
||||
})
|
||||
audit.RegisterEventNameForAudit((&TeamDeletedEvent{}).Name(), func(payload []byte) (*audit.Entry, error) {
|
||||
e := &struct {
|
||||
Team *Team `json:"team"`
|
||||
Doer *auditDoerRef `json:"doer"`
|
||||
}{}
|
||||
if err := json.Unmarshal(payload, e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
})
|
||||
audit.RegisterEventForAudit(func(e *TeamDeletedEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
Action: audit.ActionTeamDeleted,
|
||||
Actor: auditActorFromDoerRef(e.Doer),
|
||||
Actor: auditActorFromUser(e.Doer),
|
||||
Target: audit.TeamTarget(e.Team.ID),
|
||||
}, nil
|
||||
}
|
||||
})
|
||||
audit.RegisterEventForAudit(func(e *TeamMemberAddedEvent) *audit.Entry {
|
||||
return &audit.Entry{
|
||||
|
|
|
|||
|
|
@ -1217,9 +1217,13 @@ func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProje
|
|||
return err
|
||||
}
|
||||
|
||||
doer, err := GetUserOrLinkShareUser(s, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.DispatchOnCommit(s, &ProjectUpdatedEvent{
|
||||
Project: project,
|
||||
Doer: auth,
|
||||
Doer: doer,
|
||||
})
|
||||
|
||||
l, err := GetProjectSimpleByID(s, project.ID)
|
||||
|
|
@ -1448,9 +1452,13 @@ func (p *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
doer, err := GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.DispatchOnCommit(s, &ProjectDeletedEvent{
|
||||
Project: fullProject,
|
||||
Doer: a,
|
||||
Doer: doer,
|
||||
})
|
||||
|
||||
childProjects := []*Project{}
|
||||
|
|
|
|||
|
|
@ -109,10 +109,14 @@ func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
doer, err := GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.DispatchOnCommit(s, &ProjectSharedWithTeamEvent{
|
||||
Project: l,
|
||||
Team: team,
|
||||
Doer: a,
|
||||
Doer: doer,
|
||||
})
|
||||
|
||||
err = updateProjectLastUpdated(s, l)
|
||||
|
|
|
|||
|
|
@ -115,10 +115,14 @@ func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
doer, err := GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.DispatchOnCommit(s, &ProjectSharedWithUserEvent{
|
||||
Project: l,
|
||||
User: u,
|
||||
Doer: a,
|
||||
Doer: doer,
|
||||
})
|
||||
|
||||
err = updateProjectLastUpdated(s, l)
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ func (t *Team) CreateNewTeam(s *xorm.Session, a web.Auth, firstUserShouldBeAdmin
|
|||
|
||||
events.DispatchOnCommit(s, &TeamCreatedEvent{
|
||||
Team: t,
|
||||
Doer: a,
|
||||
Doer: doer,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
@ -360,9 +360,13 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
doer, err := GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.DispatchOnCommit(s, &TeamDeletedEvent{
|
||||
Team: t,
|
||||
Doer: a,
|
||||
Doer: doer,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue