feat: refresh candidate traits after CV changes
Recalculate stored participant traits when job postings change and expose replacement CV uploads with live candidate refresh events. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
30e3391adb
commit
5dbda53759
12 changed files with 373 additions and 9 deletions
|
|
@ -40,6 +40,7 @@ type IStore interface {
|
|||
ListParticipants(context.Context) ([]servicemodel.Participant, error)
|
||||
GetParticipant(context.Context, uint) (servicemodel.Participant, error)
|
||||
CreateParticipant(context.Context, servicemodel.Participant) (servicemodel.Participant, error)
|
||||
UpdateParticipant(context.Context, servicemodel.Participant) (servicemodel.Participant, error)
|
||||
AllTraits(context.Context) ([]servicemodel.Trait, error)
|
||||
ListInvitations(context.Context, uint) ([]servicemodel.Invitation, error)
|
||||
CreateInvitation(context.Context, servicemodel.Invitation) (servicemodel.Invitation, error)
|
||||
|
|
@ -328,6 +329,38 @@ func (s *Store) CreateParticipant(ctx context.Context, value servicemodel.Partic
|
|||
return s.GetParticipant(ctx, entity.ID)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateParticipant(ctx context.Context, value servicemodel.Participant) (servicemodel.Participant, error) {
|
||||
entity := dbmodel.Participant{
|
||||
ID: value.ID,
|
||||
FirstName: value.FirstName, LastName: value.LastName, Birthday: value.Birthday,
|
||||
Email: value.Email, ContactInfo: value.ContactInfo, Photo: value.Photo,
|
||||
PhotoType: value.PhotoType, CV: value.CV, CVFilename: value.CVFilename,
|
||||
}
|
||||
db := s.db(ctx)
|
||||
if err := db.Transaction(func(tx *gorm.DB) error {
|
||||
traits, err := resolveTraits(tx, value.Traits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entity.Traits = traits
|
||||
result := tx.Model(&dbmodel.Participant{}).Where("id = ?", value.ID).Updates(map[string]any{
|
||||
"first_name": value.FirstName, "last_name": value.LastName, "birthday": value.Birthday,
|
||||
"email": value.Email, "contact_info": value.ContactInfo, "photo": value.Photo,
|
||||
"photo_type": value.PhotoType, "cv": value.CV, "cv_filename": value.CVFilename,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return mapError(result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return tx.Model(&entity).Association("Traits").Replace(entity.Traits)
|
||||
}); err != nil {
|
||||
return servicemodel.Participant{}, err
|
||||
}
|
||||
return s.GetParticipant(ctx, entity.ID)
|
||||
}
|
||||
|
||||
func (s *Store) AllTraits(ctx context.Context) ([]servicemodel.Trait, error) {
|
||||
var values []dbmodel.Trait
|
||||
if err := s.db(ctx).Order("name").Find(&values).Error; err != nil {
|
||||
|
|
@ -621,6 +654,10 @@ func (s *Store) ListCalendar(ctx context.Context, userID uint) ([]servicemodel.S
|
|||
}
|
||||
|
||||
func (s *Store) resolveTraits(ctx context.Context, values []servicemodel.Trait) ([]dbmodel.Trait, error) {
|
||||
return resolveTraits(s.db(ctx), values)
|
||||
}
|
||||
|
||||
func resolveTraits(db *gorm.DB, values []servicemodel.Trait) ([]dbmodel.Trait, error) {
|
||||
result := make([]dbmodel.Trait, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
|
|
@ -634,7 +671,7 @@ func (s *Store) resolveTraits(ctx context.Context, values []servicemodel.Trait)
|
|||
}
|
||||
seen[normalized] = struct{}{}
|
||||
var trait dbmodel.Trait
|
||||
if err := s.db(ctx).Where(dbmodel.Trait{Normalized: normalized}).
|
||||
if err := db.Where(dbmodel.Trait{Normalized: normalized}).
|
||||
Attrs(dbmodel.Trait{Name: name}).FirstOrCreate(&trait).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,3 +73,37 @@ func TestStorePersistsJobWithTraits(t *testing.T) {
|
|||
t.Fatalf("unexpected stored job: %#v", found)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreUpdatesParticipantAndReplacesTraits(t *testing.T) {
|
||||
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
||||
if databaseURL == "" {
|
||||
t.Skip("TEST_DATABASE_URL is not set")
|
||||
}
|
||||
db, err := gorm.Open(postgres.Open(databaseURL), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store := New(db)
|
||||
suffix := uuid.NewString()
|
||||
created, err := store.CreateParticipant(context.Background(), model.Participant{
|
||||
FirstName: "Ada", LastName: "Lovelace", Email: "candidate-" + suffix + "@example.com",
|
||||
CV: []byte("Go developer"), CVFilename: "old.txt",
|
||||
Traits: []model.Trait{{Name: "Go"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { db.Exec("DELETE FROM participants WHERE id = ?", created.ID) })
|
||||
|
||||
created.CV = []byte("React developer")
|
||||
created.CVFilename = "new.txt"
|
||||
created.Traits = []model.Trait{{Name: "React"}}
|
||||
updated, err := store.UpdateParticipant(context.Background(), created)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if updated.ID != created.ID || updated.CVFilename != "new.txt" ||
|
||||
len(updated.Traits) != 1 || updated.Traits[0].Name != "React" {
|
||||
t.Fatalf("unexpected updated participant: %#v", updated)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue