Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions components/backend/internal/service/hackathon_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,15 +276,15 @@ func (s *HackathonService) Join(
}

// Check if user already exists in hackathon (approved or waitlisted)
_, err = s.dbClient.Participant.Query().Where(
participant, err := s.dbClient.Participant.Query().Where(
entparticipant.HackathonIDEQ(id),
entparticipant.UserID(user.ID),
).Only(ctx)
isMember := false
if err == nil {
// Already a participant - return success with existing hackathon ID
return &msgs.JoinResponse{HackathonId: h.ID.String()}, nil
}
if !ent.IsNotFound(err) {
// Already a participant - we don't need to insert, just potentially upsert answers
isMember = true
} else if !ent.IsNotFound(err) {
slog.Error("check existing participant", "err", err)

return nil, status.Error(codes.Internal, "couldn't check participant status")
Expand All @@ -302,15 +302,17 @@ func (s *HackathonService) Join(
return nil, status.Error(codes.Internal, "couldn't start transaction")
}

participant, err := tx.Participant.Create().
SetHackathonID(id).
SetUserID(user.ID).
SetIsWaiting(true).
Save(ctx)
if err != nil {
_ = tx.Rollback()
slog.Error("create participant", "err", err)
return nil, status.Errorf(codes.Internal, "couldn't join hackathon")
if !isMember {
participant, err = tx.Participant.Create().
SetHackathonID(id).
SetUserID(user.ID).
SetIsWaiting(true).
Save(ctx)
if err != nil {
_ = tx.Rollback()
slog.Error("create participant", "err", err)
return nil, status.Errorf(codes.Internal, "couldn't join hackathon")
}
}

// Upsert answers linked to the new participant
Expand All @@ -320,7 +322,7 @@ func (s *HackathonService) Join(
SetQuestionID(qID).
SetUserID(participant.UserID).
SetValue(protoAnswerValueToDB(a)).
OnConflict().
OnConflictColumns(entanswer.FieldQuestionID, entanswer.FieldUserID).
UpdateNewValues().
Exec(ctx)
if err != nil {
Expand Down Expand Up @@ -1542,7 +1544,7 @@ func (s *HackathonService) SubmitAnswers(
SetQuestionID(qID).
SetUserID(participant.UserID).
SetValue(protoAnswerValueToDB(a)).
OnConflict().
OnConflictColumns(entanswer.FieldQuestionID, entanswer.FieldUserID).
UpdateNewValues().
Exec(ctx)
if err != nil {
Expand Down
78 changes: 78 additions & 0 deletions components/backend/internal/service/hackathon_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,84 @@ var _ = Describe("HackathonService", func() {
Expect(participant.IsWaiting).To(BeTrue())
})

It("upserts answers when user joins twice with different answers", func() {
// Create a question first
token := testutils.CreateTestJWTToken(testAdmin)
adminCtx := metadata.NewOutgoingContext(
context.Background(),
metadata.Pairs("authorization", "Bearer "+token),
)
qResp, err := client.CreateQuestion(adminCtx, &msgs.CreateQuestionRequest{
HackathonId: createdHackathonID,
Key: "company",
Label: "Company",
Type: entities.QuestionType_QUESTION_TYPE_TEXT,
Mandatory: true,
Order: 1,
})
Expect(err).NotTo(HaveOccurred())
questionID := qResp.GetQuestionId()

// Create a user
joinUserKeycloakID := "join-upsert-user"
joinUser, err := dbClient.User.Create().
SetKeycloakID(joinUserKeycloakID).
SetUsername("join-upsert-username").
Save(context.Background())
Expect(err).NotTo(HaveOccurred())

// Join first time with answer "Acme Corp"
joinToken := testutils.CreateTestJWTToken(joinUserKeycloakID)
joinCtx := metadata.NewOutgoingContext(
context.Background(),
metadata.Pairs("authorization", "Bearer "+joinToken),
)
_, err = client.Join(joinCtx, &msgs.JoinRequest{
HackathonId: createdHackathonID,
Answers: []*entities.Answer{
{
QuestionId: questionID,
Value: &entities.Answer_TextValue{TextValue: "Acme Corp"},
},
},
})
Expect(err).NotTo(HaveOccurred())

// Verify first answer
answers, err := dbClient.Answer.Query().
Where(
entanswer.QuestionIDEQ(uuid.MustParse(questionID)),
entanswer.UserID(joinUser.ID),
).All(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(answers).To(HaveLen(1))
Expect(answers[0].Value).To(Equal("Acme Corp"))

// Join again with different answer "Globex Inc"
_, err = client.Join(joinCtx, &msgs.JoinRequest{
HackathonId: createdHackathonID,
Answers: []*entities.Answer{
{
QuestionId: questionID,
Value: &entities.Answer_TextValue{TextValue: "Globex Inc"},
},
},
})
Expect(err).NotTo(HaveOccurred())

// Verify answer was upserted (not duplicated)
answers, err = dbClient.Answer.Query().
Where(
entanswer.QuestionIDEQ(uuid.MustParse(questionID)),
entanswer.UserID(joinUser.ID),
).All(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(answers).To(HaveLen(1), "should have exactly one answer, not duplicate")
Expect(
answers[0].Value,
).To(Equal("Globex Inc"), "answer should be updated to new value")
})

It("returns NOT_FOUND for invalid hackathon ID", func() {
token := testutils.CreateTestJWTToken(testAdmin)
ctx := metadata.NewOutgoingContext(
Expand Down
Loading