···5353func (l *Labels) Router(mw *middleware.Middleware) http.Handler {5454 r := chi.NewRouter()55555656- r.With(middleware.AuthMiddleware(l.oauth)).Put("/perform", l.PerformLabelOp)5656+ r.Use(middleware.AuthMiddleware(l.oauth))5757+ r.Put("/perform", l.PerformLabelOp)57585859 return r5960}60616262+// this is a tricky handler implementation:6363+// - the user selects the new state of all the labels in the label panel and hits save6464+// - this handler should calculate the diff in order to create the labelop record6565+// - we need the diff in order to maintain a "history" of operations performed by users6166func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) {6267 user := l.oauth.GetUser(r)63686969+ noticeId := "add-label-error"7070+7171+ fail := func(msg string, err error) {7272+ l.logger.Error("failed to add label", "err", err)7373+ l.pages.Notice(w, noticeId, msg)7474+ }7575+6476 if err := r.ParseForm(); err != nil {6565- l.logger.Error("failed to parse form data", "error", err)6666- http.Error(w, "Invalid form data", http.StatusBadRequest)7777+ fail("Invalid form.", err)6778 return6879 }6980···8473 indexedAt := time.Now()8574 repoAt := r.Form.Get("repo")8675 subjectUri := r.Form.Get("subject")8787- keys := r.Form["operand-key"]8888- vals := r.Form["operand-val"]8989-9090- var labelOps []db.LabelOp9191- for i := range len(keys) {9292- op := r.FormValue(fmt.Sprintf("op-%d", i))9393- if op == "" {9494- op = string(db.LabelOperationDel)9595- }9696- key := keys[i]9797- val := vals[i]9898-9999- labelOps = append(labelOps, db.LabelOp{100100- Did: did,101101- Rkey: rkey,102102- Subject: syntax.ATURI(subjectUri),103103- Operation: db.LabelOperation(op),104104- OperandKey: key,105105- OperandValue: val,106106- PerformedAt: performedAt,107107- IndexedAt: indexedAt,108108- })109109- }1107611177 // find all the labels that this repo subscribes to11278 repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt))11379 if err != nil {114114- http.Error(w, "Invalid form data", http.StatusBadRequest)8080+ fail("Failed to get labels for this repository.", err)11581 return11682 }11783···99111100112 actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts))101113 if err != nil {102102- http.Error(w, "Invalid form data", http.StatusBadRequest)114114+ fail("Invalid form data.", err)103115 return104116 }105117106106- for i := range labelOps {107107- def := actx.Defs[labelOps[i].OperandKey]108108- if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil {109109- l.logger.Error("form failed to validate", "err", err)110110- http.Error(w, "Invalid form data", http.StatusBadRequest)111111- return112112- }113113-114114- l.logger.Info("value changed to: ", "v", labelOps[i].OperandValue)115115- }118118+ l.logger.Info("actx", "labels", labelAts)119119+ l.logger.Info("actx", "defs", actx.Defs)116120117121 // calculate the start state by applying already known labels118122 existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))119123 if err != nil {120120- http.Error(w, "Invalid form data", http.StatusBadRequest)124124+ fail("Invalid form data.", err)121125 return122126 }123127124128 labelState := db.NewLabelState()125129 actx.ApplyLabelOps(labelState, existingOps)126130127127- l.logger.Info("state", "state", labelState)131131+ var labelOps []db.LabelOp132132+133133+ // first delete all existing state134134+ for key, vals := range labelState.Inner() {135135+ for val := range vals {136136+ labelOps = append(labelOps, db.LabelOp{137137+ Did: did,138138+ Rkey: rkey,139139+ Subject: syntax.ATURI(subjectUri),140140+ Operation: db.LabelOperationDel,141141+ OperandKey: key,142142+ OperandValue: val,143143+ PerformedAt: performedAt,144144+ IndexedAt: indexedAt,145145+ })146146+ }147147+ }148148+149149+ // add all the new state the user specified150150+ for key, vals := range r.Form {151151+ if _, ok := actx.Defs[key]; !ok {152152+ continue153153+ }154154+155155+ for _, val := range vals {156156+ labelOps = append(labelOps, db.LabelOp{157157+ Did: did,158158+ Rkey: rkey,159159+ Subject: syntax.ATURI(subjectUri),160160+ Operation: db.LabelOperationAdd,161161+ OperandKey: key,162162+ OperandValue: val,163163+ PerformedAt: performedAt,164164+ IndexedAt: indexedAt,165165+ })166166+ }167167+ }168168+169169+ // reduce the opset170170+ labelOps = db.ReduceLabelOps(labelOps)171171+172172+ for i := range labelOps {173173+ def := actx.Defs[labelOps[i].OperandKey]174174+ if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil {175175+ fail(fmt.Sprintf("Invalid form data: %s", err), err)176176+ return177177+ }178178+ }128179129180 // next, apply all ops introduced in this request and filter out ones that are no-ops130181 validLabelOps := labelOps[:0]···184157185158 client, err := l.oauth.AuthorizedClient(r)186159 if err != nil {187187- l.logger.Error("failed to create client", "error", err)188188- http.Error(w, "Invalid form data", http.StatusBadRequest)160160+ fail("Failed to authorize user.", err)189161 return190162 }191163···197171 },198172 })199173 if err != nil {200200- l.logger.Error("failed to write to PDS", "error", err)201201- http.Error(w, "failed to write to PDS", http.StatusInternalServerError)174174+ fail("Failed to create record on PDS for user.", err)202175 return203176 }204177 atUri := resp.Uri205178206179 tx, err := l.db.BeginTx(r.Context(), nil)207180 if err != nil {208208- l.logger.Error("failed to start tx", "error", err)181181+ fail("Failed to update labels. Try again later.", err)209182 return210183 }211184···225200226201 for _, o := range validLabelOps {227202 if _, err := db.AddLabelOp(l.db, &o); err != nil {228228- l.logger.Error("failed to add op", "err", err)203203+ fail("Failed to update labels. Try again later.", err)229204 return230205 }231231-232232- l.logger.Info("performed label op", "did", o.Did, "rkey", o.Rkey, "kind", o.Operation, "subjcet", o.Subject, "key", o.OperandKey)233206 }234207235208 err = tx.Commit()