mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
feat(activity): per-tenant audit feed in notification dropdown
activity_events records booking lifecycle transitions with the actor behind each change — staff (dashboard), customer (self-service), or system (auto-promotion). The notification dropdown leads with the real feed instead of derived recent-booking guesses. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e5cb9bd543
commit
d86cba61e3
18 files changed
+634
-28
No files matched your search
@@ -0,0 +1,55 @@
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"bookra/apps/backend/internal/db"
|
||||
"bookra/apps/backend/internal/domain"
|
||||
)
|
||||
|
||||
var ErrTenantMembership = errors.New("tenant membership not found")
|
||||
|
||||
const defaultLimit = 50
|
||||
const maxLimit = 100
|
||||
|
||||
type Service struct {
|
||||
repo db.Repository
|
||||
}
|
||||
|
||||
func NewService(repo db.Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
// List returns the tenant's recent activity, newest first. The feed is
|
||||
// tenant-scoped — every dashboard user sees the same audit trail.
|
||||
func (s *Service) List(ctx context.Context, principal domain.Principal, limit int) (domain.ActivityFeedResponse, error) {
|
||||
membership, err := s.repo.GetTenantMembershipBySubject(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.ActivityFeedResponse{}, ErrTenantMembership
|
||||
}
|
||||
if limit <= 0 || limit > maxLimit {
|
||||
limit = defaultLimit
|
||||
}
|
||||
records, err := s.repo.ListActivityEvents(ctx, membership.Tenant.ID, limit)
|
||||
if err != nil {
|
||||
return domain.ActivityFeedResponse{}, err
|
||||
}
|
||||
events := make([]domain.ActivityEvent, 0, len(records))
|
||||
for _, rec := range records {
|
||||
event := domain.ActivityEvent{
|
||||
ID: rec.ID,
|
||||
ActorKind: rec.ActorKind,
|
||||
ActorName: rec.ActorName,
|
||||
Action: rec.Action,
|
||||
EntityType: rec.EntityType,
|
||||
Summary: rec.Summary,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}
|
||||
if rec.EntityID != nil {
|
||||
event.EntityID = *rec.EntityID
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
return domain.ActivityFeedResponse{Events: events}, nil
|
||||
}
|
||||
Reference in new issue
Block a user