create employee table
Some checks failed
CI / test (push) Failing after 4m38s
CI / fixture (push) Failing after 9s

This commit is contained in:
2026-09-05 10:44:47 +05:30
parent dc785b917c
commit cf99866e12
11 changed files with 820 additions and 26 deletions

View File

@@ -57,8 +57,15 @@ func DeterministicUUID(name string) string {
}
// Fixture is the serialised frontend dataset.
//
// Users and DemoUser both name accounts, and both are read. Users is the list
// the generator writes today; DemoUser is the single account older fixtures
// carry, and is kept so a fixture written before the list existed still seeds.
// Where both are present the list wins and DemoUser is folded into it by id, so
// the demo administrator is written once rather than twice.
type Fixture struct {
DemoUser map[string]any `json:"demoUser"`
Users []map[string]any `json:"users"`
Entities map[string][]map[string]any `json:"entities"`
}
@@ -165,7 +172,7 @@ func (s *Seeder) Run(ctx context.Context) (*Result, error) {
}
result := &Result{OrgID: orgID, Counts: map[string]int{}}
n, err := s.upsertUser(ctx, tx, orgID)
n, err := s.upsertUsers(ctx, tx, orgID)
if err != nil {
return nil, err
}
@@ -218,13 +225,49 @@ func (s *Seeder) upsertOrganization(ctx context.Context, tx pgx.Tx) (string, err
return id, err
}
// upsertUser writes the demo user and splits its preferences into their own
// table, as api-contract.md §9 describes.
func (s *Seeder) upsertUser(ctx context.Context, tx pgx.Tx, orgID string) (int, error) {
u := s.fixture.DemoUser
if u == nil {
return 0, nil
// seedUsers is the accounts to write, in fixture order and deduplicated by the
// legacy id the deterministic UUID is derived from.
//
// It used to be one account. That was not a simplification, it was the reason
// two thirds of the authorization table had never been exercised: `policy.go`
// has always had three roles and the fixture has always had one administrator,
// so there was nobody to sign in as to reach the employer console at all.
func (s *Seeder) seedUsers() []map[string]any {
seen := map[string]bool{}
var out []map[string]any
for _, u := range append(append([]map[string]any{}, s.fixture.Users...), s.fixture.DemoUser) {
if u == nil {
continue
}
legacy, _ := u["id"].(string)
if seen[legacy] {
continue
}
seen[legacy] = true
out = append(out, u)
}
return out
}
// upsertUsers writes the seeded accounts and splits their preferences into
// their own table, as api-contract.md §9 describes.
//
// Note what the ON CONFLICT set list below does NOT touch: password_hash. A
// re-seed refreshes who someone is and leaves them able to sign in, which is
// what makes `cmd/setpassword` a one-time action per account rather than a step
// after every `make seed`.
func (s *Seeder) upsertUsers(ctx context.Context, tx pgx.Tx, orgID string) (int, error) {
count := 0
for _, u := range s.seedUsers() {
if err := s.upsertUser(ctx, tx, orgID, u); err != nil {
return count, err
}
count++
}
return count, nil
}
func (s *Seeder) upsertUser(ctx context.Context, tx pgx.Tx, orgID string, u map[string]any) error {
legacy, _ := u["id"].(string)
id := DeterministicUUID("User:" + legacy)
created := stringOr(u["created_date"], iso(s.now))
@@ -240,7 +283,7 @@ func (s *Seeder) upsertUser(ctx context.Context, tx pgx.Tx, orgID string) (int,
stringOr(u["email"], ""), stringOr(u["full_name"], ""),
stringOr(u["role"], "admin"), stringOr(u["account_type"], "employer"), created)
if err != nil {
return 0, err
return err
}
prefs, _ := u["preferences"].(map[string]any)
@@ -257,7 +300,7 @@ func (s *Seeder) upsertUser(ctx context.Context, tx pgx.Tx, orgID string) (int,
}
extraJSON, err := json.Marshal(extra)
if err != nil {
return 0, err
return err
}
_, err = tx.Exec(ctx,
`INSERT INTO user_preferences (user_id, owliver_default, compact_density, email_digest, extra)
@@ -271,9 +314,9 @@ func (s *Seeder) upsertUser(ctx context.Context, tx pgx.Tx, orgID string) (int,
id, boolOr(prefs["owliverDefault"], true), boolOr(prefs["compactDensity"], false),
boolOr(prefs["emailDigest"], true), extraJSON)
if err != nil {
return 0, err
return err
}
return 1, nil
return nil
}
// pruneShiftRecords deletes this organization's shift rows that this run did