{"record":{"id":"7ed378876831f3bb","repo":"apache/answer","slug":"badge-already-awarded","errorCode":null,"errorMessage":"badge already awarded","messagePattern":"badge already awarded","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/repo/badge_award/badge_award_repo.go","lineNumber":80,"sourceCode":"\t\t}\n\t\tif !exist {\n\t\t\treturn nil, fmt.Errorf(\"badge not exist\")\n\t\t}\n\n\t\told := &entity.BadgeAward{\n\t\t\tUserID:         badgeAward.UserID,\n\t\t\tBadgeID:        badgeAward.BadgeID,\n\t\t\tIsBadgeDeleted: entity.IsBadgeNotDeleted,\n\t\t}\n\t\tif badgeInfo.Single != entity.BadgeSingleAward {\n\t\t\told.AwardKey = badgeAward.AwardKey\n\t\t}\n\t\texist, err = session.Get(old)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif exist {\n\t\t\treturn nil, fmt.Errorf(\"badge already awarded\")\n\t\t}\n\n\t\t_, err = session.Insert(badgeAward)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn session.ID(badgeInfo.ID).Incr(\"award_count\", 1).Update(&entity.Badge{})\n\t})\n\tif err != nil {\n\t\treturn errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()\n\t}\n\treturn nil\n}\n\n// CheckIsAward check this badge is awarded for this user or not\nfunc (r *badgeAwardRepo) CheckIsAward(ctx context.Context, badgeID, userID, awardKey string, singleOrMulti int8) (\n\tisAward bool, err error) {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/apache/answer/blob/3b9f1370612e690a0b7f230f05e688930db4c6d3/internal/repo/badge_award/badge_award_repo.go#L62-L98","documentation":"Thrown when a Get on the badge_award table (user_id + badge_id + not-deleted) already returns a row, i.e. this user has already been awarded this badge. The repo refuses to insert a duplicate award. It is an intentional uniqueness guard, not a data-corruption signal.","triggerScenarios":"Calling the badge-award path twice for the same (UserID, BadgeID) pair — e.g. a retry after a slow first response, or an automated trigger plus a manual award.","commonSituations":"Event handlers firing more than once (re-delivered webhook/queue message); admin manually granting a badge the user already earned; missing idempotency in import scripts.","solutions":["Check for an existing award before calling, or catch this error and treat it as success (idempotent award).","Make the trigger/caller deduplicate: query badge_award for (user_id, badge_id) first.","If re-award is legitimate business-wise, remove/restore the old award instead of inserting a new one.","Add a unique index on (user_id, badge_id) so the DB enforces the same rule."],"exampleFix":"// before\nexist, err = session.Get(old)\nif exist {\n    return nil, fmt.Errorf(\"badge already awarded\")\n}\n// after\nexist, err = session.Get(old)\nif err != nil {\n    return nil, err\n}\nif exist {\n    return old, nil // idempotent: return existing award instead of error\n}","handlingStrategy":"validation","validationCode":"var existing entity.BadgeAward\nhas, err := db.Where(\"user_id = ? AND badge_id = ?\", userID, badgeID).Get(&existing)\nif err == nil && has {\n    // already awarded — skip or return existing without inserting\n    return &existing, nil\n}","typeGuard":"func alreadyAwarded(awards []entity.BadgeAward, userID, badgeID int64) bool {\n    for _, a := range awards {\n        if a.UserID == userID && a.BadgeID == badgeID {\n            return true\n        }\n    }\n    return false\n}","tryCatchPattern":"award, err := badgeAwardRepo.Award(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"badge already awarded\") {\n        return nil // idempotent success\n    }\n    return err\n}","preventionTips":["Make award operations idempotent: dedupe by (user_id, badge_id) before inserting.","Guard queue/webhook handlers against redelivery (idempotency keys).","Add a unique index on (user_id, badge_id).","Do not issue the same award from both an automated trigger and a manual admin action."],"tags":["database","duplicate","badge","idempotency"],"backgroundTag":"duplicate-record-conflict","analyzedSha":"3b9f1370612e690a0b7f230f05e688930db4c6d3","analyzedAt":"2026-09-05T18:18:39.533Z","contentChangedAt":"2026-09-05T18:18:39.533Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}