Files
BountyBoard/internal/domain/user.go
T
etalon b1c9a42810 feat: metrics dashboards with SVG charts, CSV export, leaderboard (phase 10)
- aggregations over the immutable bountyAwards ledger: totals, weekly
  buckets ($dateTrunc), per-developer and per-customer groupings
- timeline-derived: approval rate, time logged, assigned→approved lead
  time, imported→published atomization lead time, open board depth
- developer + consultant dashboards (admin = global consultant view),
  date-range filters, CSV export of the ledger
- leaderboard (top 10 by bounty) honoring the new leaderboardOptOut
  profile setting
- hand-rolled SVG line and bar charts (~150 lines, no chart library)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:19:57 +02:00

84 lines
2.7 KiB
Go

// Package domain holds the core data model types and business rules shared
// across handlers, stores, and workers. It has no Mongo or HTTP dependencies
// beyond bson struct tags.
package domain
import "time"
type Roles struct {
Admin bool `bson:"admin" json:"admin"`
Consultant bool `bson:"consultant" json:"consultant"`
Developer bool `bson:"developer" json:"developer"`
}
// Has reports whether the role named r is set. Known names: admin,
// consultant, developer.
func (r Roles) Has(role string) bool {
switch role {
case "admin":
return r.Admin
case "consultant":
return r.Consultant
case "developer":
return r.Developer
default:
return false
}
}
type LocalAuth struct {
PasswordHash string `bson:"passwordHash" json:"-"`
MustChange bool `bson:"mustChange" json:"mustChange"`
}
type OIDCAuth struct {
Issuer string `bson:"issuer" json:"issuer"`
Subject string `bson:"subject" json:"subject"`
}
type Auth struct {
Local *LocalAuth `bson:"local" json:"local,omitempty"`
OIDC *OIDCAuth `bson:"oidc" json:"oidc,omitempty"`
}
type Contact struct {
Phone string `bson:"phone" json:"phone"`
Location string `bson:"location" json:"location"`
Links []string `bson:"links" json:"links"`
}
type NotificationPrefs struct {
Email bool `bson:"email" json:"email"`
InApp bool `bson:"inApp" json:"inApp"`
}
type UserSettings struct {
Theme string `bson:"theme" json:"theme"`
Notifications NotificationPrefs `bson:"notifications" json:"notifications"`
LeaderboardOptOut bool `bson:"leaderboardOptOut" json:"leaderboardOptOut"`
}
type User struct {
ID string `bson:"_id" json:"id"`
Email string `bson:"email" json:"email"`
Name string `bson:"name" json:"name"`
AvatarFileID string `bson:"avatarFileId,omitempty" json:"avatarFileId,omitempty"`
Bio string `bson:"bio" json:"bio"`
Contact Contact `bson:"contact" json:"contact"`
Extra map[string]any `bson:"extra" json:"extra"`
Roles Roles `bson:"roles" json:"roles"`
Auth Auth `bson:"auth" json:"-"`
Settings UserSettings `bson:"settings" json:"settings"`
Disabled bool `bson:"disabled" json:"disabled"`
LastSeenAt time.Time `bson:"lastSeenAt" json:"lastSeenAt"`
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
Version int64 `bson:"version" json:"version"`
}
// MustChangePassword reports whether the user is locked to the
// password-change flow.
func (u *User) MustChangePassword() bool {
return u.Auth.Local != nil && u.Auth.Local.MustChange
}