···8182func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
83 l := rp.logger.With("handler", "RepoSingleIssue")
84- user := rp.oauth.GetUser(r)
85 f, err := rp.repoResolver.Resolve(r)
86 if err != nil {
87 l.Error("failed to get repo and knot", "err", err)
···102103 userReactions := map[models.ReactionKind]bool{}
104 if user != nil {
105- userReactions = db.GetReactionStatusMap(rp.db, user.Did, issue.AtUri())
106 }
107108 backlinks, err := db.GetBacklinks(rp.db, issue.AtUri())
···143144func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) {
145 l := rp.logger.With("handler", "EditIssue")
146- user := rp.oauth.GetUser(r)
147148 issue, ok := r.Context().Value("issue").(*models.Issue)
149 if !ok {
···182 return
183 }
184185- ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueNSID, user.Did, newIssue.Rkey)
186 if err != nil {
187 l.Error("failed to get record", "err", err)
188 rp.pages.Notice(w, noticeId, "Failed to edit issue, no record found on PDS.")
···191192 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
193 Collection: tangled.RepoIssueNSID,
194- Repo: user.Did,
195 Rkey: newIssue.Rkey,
196 SwapRecord: ex.Cid,
197 Record: &lexutil.LexiconTypeDecoder{
···292293func (rp *Issues) CloseIssue(w http.ResponseWriter, r *http.Request) {
294 l := rp.logger.With("handler", "CloseIssue")
295- user := rp.oauth.GetUser(r)
296 f, err := rp.repoResolver.Resolve(r)
297 if err != nil {
298 l.Error("failed to get repo and knot", "err", err)
···306 return
307 }
308309- roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
310 isRepoOwner := roles.IsOwner()
311 isCollaborator := roles.IsCollaborator()
312- isIssueOwner := user.Did == issue.Did
313314 // TODO: make this more granular
315 if isIssueOwner || isRepoOwner || isCollaborator {
···326 issue.Open = false
327328 // notify about the issue closure
329- rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Did), issue)
330331 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
332 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···340341func (rp *Issues) ReopenIssue(w http.ResponseWriter, r *http.Request) {
342 l := rp.logger.With("handler", "ReopenIssue")
343- user := rp.oauth.GetUser(r)
344 f, err := rp.repoResolver.Resolve(r)
345 if err != nil {
346 l.Error("failed to get repo and knot", "err", err)
···354 return
355 }
356357- roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
358 isRepoOwner := roles.IsOwner()
359 isCollaborator := roles.IsCollaborator()
360- isIssueOwner := user.Did == issue.Did
361362 if isCollaborator || isRepoOwner || isIssueOwner {
363 err := db.ReopenIssues(
···373 issue.Open = true
374375 // notify about the issue reopen
376- rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Did), issue)
377378 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
379 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···387388func (rp *Issues) NewIssueComment(w http.ResponseWriter, r *http.Request) {
389 l := rp.logger.With("handler", "NewIssueComment")
390- user := rp.oauth.GetUser(r)
391 f, err := rp.repoResolver.Resolve(r)
392 if err != nil {
393 l.Error("failed to get repo and knot", "err", err)
···416 mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
417418 comment := models.IssueComment{
419- Did: user.Did,
420 Rkey: tid.TID(),
421 IssueAt: issue.AtUri().String(),
422 ReplyTo: replyTo,
···495496func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) {
497 l := rp.logger.With("handler", "IssueComment")
498- user := rp.oauth.GetUser(r)
499500 issue, ok := r.Context().Value("issue").(*models.Issue)
501 if !ok {
···531532func (rp *Issues) EditIssueComment(w http.ResponseWriter, r *http.Request) {
533 l := rp.logger.With("handler", "EditIssueComment")
534- user := rp.oauth.GetUser(r)
535536 issue, ok := r.Context().Value("issue").(*models.Issue)
537 if !ok {
···557 }
558 comment := comments[0]
559560- if comment.Did != user.Did {
561- l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Did)
562 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
563 return
564 }
···608 // rkey is optional, it was introduced later
609 if newComment.Rkey != "" {
610 // update the record on pds
611- ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, comment.Rkey)
612 if err != nil {
613 l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
614 rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
···617618 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
619 Collection: tangled.RepoIssueCommentNSID,
620- Repo: user.Did,
621 Rkey: newComment.Rkey,
622 SwapRecord: ex.Cid,
623 Record: &lexutil.LexiconTypeDecoder{
···641642func (rp *Issues) ReplyIssueCommentPlaceholder(w http.ResponseWriter, r *http.Request) {
643 l := rp.logger.With("handler", "ReplyIssueCommentPlaceholder")
644- user := rp.oauth.GetUser(r)
645646 issue, ok := r.Context().Value("issue").(*models.Issue)
647 if !ok {
···677678func (rp *Issues) ReplyIssueComment(w http.ResponseWriter, r *http.Request) {
679 l := rp.logger.With("handler", "ReplyIssueComment")
680- user := rp.oauth.GetUser(r)
681682 issue, ok := r.Context().Value("issue").(*models.Issue)
683 if !ok {
···713714func (rp *Issues) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
715 l := rp.logger.With("handler", "DeleteIssueComment")
716- user := rp.oauth.GetUser(r)
717718 issue, ok := r.Context().Value("issue").(*models.Issue)
719 if !ok {
···739 }
740 comment := comments[0]
741742- if comment.Did != user.Did {
743- l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Did)
744 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
745 return
746 }
···769 }
770 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
771 Collection: tangled.RepoIssueCommentNSID,
772- Repo: user.Did,
773 Rkey: comment.Rkey,
774 })
775 if err != nil {
···807808 page := pagination.FromContext(r.Context())
809810- user := rp.oauth.GetUser(r)
811 f, err := rp.repoResolver.Resolve(r)
812 if err != nil {
813 l.Error("failed to get repo and knot", "err", err)
···884 }
885886 rp.pages.RepoIssues(w, pages.RepoIssuesParams{
887- LoggedInUser: rp.oauth.GetUser(r),
888 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
889 Issues: issues,
890 IssueCount: totalIssues,
···897898func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) {
899 l := rp.logger.With("handler", "NewIssue")
900- user := rp.oauth.GetUser(r)
901902 f, err := rp.repoResolver.Resolve(r)
903 if err != nil {
···921 Title: r.FormValue("title"),
922 Body: body,
923 Open: true,
924- Did: user.Did,
925 Created: time.Now(),
926 Mentions: mentions,
927 References: references,
···945 }
946 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
947 Collection: tangled.RepoIssueNSID,
948- Repo: user.Did,
949 Rkey: issue.Rkey,
950 Record: &lexutil.LexiconTypeDecoder{
951 Val: &record,
···8182func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
83 l := rp.logger.With("handler", "RepoSingleIssue")
84+ user := rp.oauth.GetMultiAccountUser(r)
85 f, err := rp.repoResolver.Resolve(r)
86 if err != nil {
87 l.Error("failed to get repo and knot", "err", err)
···102103 userReactions := map[models.ReactionKind]bool{}
104 if user != nil {
105+ userReactions = db.GetReactionStatusMap(rp.db, user.Active.Did, issue.AtUri())
106 }
107108 backlinks, err := db.GetBacklinks(rp.db, issue.AtUri())
···143144func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) {
145 l := rp.logger.With("handler", "EditIssue")
146+ user := rp.oauth.GetMultiAccountUser(r)
147148 issue, ok := r.Context().Value("issue").(*models.Issue)
149 if !ok {
···182 return
183 }
184185+ ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueNSID, user.Active.Did, newIssue.Rkey)
186 if err != nil {
187 l.Error("failed to get record", "err", err)
188 rp.pages.Notice(w, noticeId, "Failed to edit issue, no record found on PDS.")
···191192 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
193 Collection: tangled.RepoIssueNSID,
194+ Repo: user.Active.Did,
195 Rkey: newIssue.Rkey,
196 SwapRecord: ex.Cid,
197 Record: &lexutil.LexiconTypeDecoder{
···292293func (rp *Issues) CloseIssue(w http.ResponseWriter, r *http.Request) {
294 l := rp.logger.With("handler", "CloseIssue")
295+ user := rp.oauth.GetMultiAccountUser(r)
296 f, err := rp.repoResolver.Resolve(r)
297 if err != nil {
298 l.Error("failed to get repo and knot", "err", err)
···306 return
307 }
308309+ roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
310 isRepoOwner := roles.IsOwner()
311 isCollaborator := roles.IsCollaborator()
312+ isIssueOwner := user.Active.Did == issue.Did
313314 // TODO: make this more granular
315 if isIssueOwner || isRepoOwner || isCollaborator {
···326 issue.Open = false
327328 // notify about the issue closure
329+ rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Active.Did), issue)
330331 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
332 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···340341func (rp *Issues) ReopenIssue(w http.ResponseWriter, r *http.Request) {
342 l := rp.logger.With("handler", "ReopenIssue")
343+ user := rp.oauth.GetMultiAccountUser(r)
344 f, err := rp.repoResolver.Resolve(r)
345 if err != nil {
346 l.Error("failed to get repo and knot", "err", err)
···354 return
355 }
356357+ roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
358 isRepoOwner := roles.IsOwner()
359 isCollaborator := roles.IsCollaborator()
360+ isIssueOwner := user.Active.Did == issue.Did
361362 if isCollaborator || isRepoOwner || isIssueOwner {
363 err := db.ReopenIssues(
···373 issue.Open = true
374375 // notify about the issue reopen
376+ rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Active.Did), issue)
377378 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
379 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···387388func (rp *Issues) NewIssueComment(w http.ResponseWriter, r *http.Request) {
389 l := rp.logger.With("handler", "NewIssueComment")
390+ user := rp.oauth.GetMultiAccountUser(r)
391 f, err := rp.repoResolver.Resolve(r)
392 if err != nil {
393 l.Error("failed to get repo and knot", "err", err)
···416 mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
417418 comment := models.IssueComment{
419+ Did: user.Active.Did,
420 Rkey: tid.TID(),
421 IssueAt: issue.AtUri().String(),
422 ReplyTo: replyTo,
···495496func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) {
497 l := rp.logger.With("handler", "IssueComment")
498+ user := rp.oauth.GetMultiAccountUser(r)
499500 issue, ok := r.Context().Value("issue").(*models.Issue)
501 if !ok {
···531532func (rp *Issues) EditIssueComment(w http.ResponseWriter, r *http.Request) {
533 l := rp.logger.With("handler", "EditIssueComment")
534+ user := rp.oauth.GetMultiAccountUser(r)
535536 issue, ok := r.Context().Value("issue").(*models.Issue)
537 if !ok {
···557 }
558 comment := comments[0]
559560+ if comment.Did != user.Active.Did {
561+ l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Active.Did)
562 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
563 return
564 }
···608 // rkey is optional, it was introduced later
609 if newComment.Rkey != "" {
610 // update the record on pds
611+ ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Active.Did, comment.Rkey)
612 if err != nil {
613 l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
614 rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
···617618 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
619 Collection: tangled.RepoIssueCommentNSID,
620+ Repo: user.Active.Did,
621 Rkey: newComment.Rkey,
622 SwapRecord: ex.Cid,
623 Record: &lexutil.LexiconTypeDecoder{
···641642func (rp *Issues) ReplyIssueCommentPlaceholder(w http.ResponseWriter, r *http.Request) {
643 l := rp.logger.With("handler", "ReplyIssueCommentPlaceholder")
644+ user := rp.oauth.GetMultiAccountUser(r)
645646 issue, ok := r.Context().Value("issue").(*models.Issue)
647 if !ok {
···677678func (rp *Issues) ReplyIssueComment(w http.ResponseWriter, r *http.Request) {
679 l := rp.logger.With("handler", "ReplyIssueComment")
680+ user := rp.oauth.GetMultiAccountUser(r)
681682 issue, ok := r.Context().Value("issue").(*models.Issue)
683 if !ok {
···713714func (rp *Issues) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
715 l := rp.logger.With("handler", "DeleteIssueComment")
716+ user := rp.oauth.GetMultiAccountUser(r)
717718 issue, ok := r.Context().Value("issue").(*models.Issue)
719 if !ok {
···739 }
740 comment := comments[0]
741742+ if comment.Did != user.Active.Did {
743+ l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Active.Did)
744 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
745 return
746 }
···769 }
770 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
771 Collection: tangled.RepoIssueCommentNSID,
772+ Repo: user.Active.Did,
773 Rkey: comment.Rkey,
774 })
775 if err != nil {
···807808 page := pagination.FromContext(r.Context())
809810+ user := rp.oauth.GetMultiAccountUser(r)
811 f, err := rp.repoResolver.Resolve(r)
812 if err != nil {
813 l.Error("failed to get repo and knot", "err", err)
···884 }
885886 rp.pages.RepoIssues(w, pages.RepoIssuesParams{
887+ LoggedInUser: rp.oauth.GetMultiAccountUser(r),
888 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
889 Issues: issues,
890 IssueCount: totalIssues,
···897898func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) {
899 l := rp.logger.With("handler", "NewIssue")
900+ user := rp.oauth.GetMultiAccountUser(r)
901902 f, err := rp.repoResolver.Resolve(r)
903 if err != nil {
···921 Title: r.FormValue("title"),
922 Body: body,
923 Open: true,
924+ Did: user.Active.Did,
925 Created: time.Now(),
926 Mentions: mentions,
927 References: references,
···945 }
946 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
947 Collection: tangled.RepoIssueNSID,
948+ Repo: user.Active.Did,
949 Rkey: issue.Rkey,
950 Record: &lexutil.LexiconTypeDecoder{
951 Val: &record,
+31-31
appview/knots/knots.go
···70}
7172func (k *Knots) knots(w http.ResponseWriter, r *http.Request) {
73- user := k.OAuth.GetUser(r)
74 registrations, err := db.GetRegistrations(
75 k.Db,
76- orm.FilterEq("did", user.Did),
77 )
78 if err != nil {
79 k.Logger.Error("failed to fetch knot registrations", "err", err)
···92func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
93 l := k.Logger.With("handler", "dashboard")
9495- user := k.OAuth.GetUser(r)
96- l = l.With("user", user.Did)
9798 domain := chi.URLParam(r, "domain")
99 if domain == "" {
···103104 registrations, err := db.GetRegistrations(
105 k.Db,
106- orm.FilterEq("did", user.Did),
107 orm.FilterEq("domain", domain),
108 )
109 if err != nil {
···154}
155156func (k *Knots) register(w http.ResponseWriter, r *http.Request) {
157- user := k.OAuth.GetUser(r)
158 l := k.Logger.With("handler", "register")
159160 noticeId := "register-error"
···175 return
176 }
177 l = l.With("domain", domain)
178- l = l.With("user", user.Did)
179180 tx, err := k.Db.Begin()
181 if err != nil {
···188 k.Enforcer.E.LoadPolicy()
189 }()
190191- err = db.AddKnot(tx, domain, user.Did)
192 if err != nil {
193 l.Error("failed to insert", "err", err)
194 fail()
···210 return
211 }
212213- ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Did, domain)
214 var exCid *string
215 if ex != nil {
216 exCid = ex.Cid
···219 // re-announce by registering under same rkey
220 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
221 Collection: tangled.KnotNSID,
222- Repo: user.Did,
223 Rkey: domain,
224 Record: &lexutil.LexiconTypeDecoder{
225 Val: &tangled.Knot{
···250 }
251252 // begin verification
253- err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
254 if err != nil {
255 l.Error("verification failed", "err", err)
256 k.Pages.HxRefresh(w)
257 return
258 }
259260- err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
261 if err != nil {
262 l.Error("failed to mark verified", "err", err)
263 k.Pages.HxRefresh(w)
···275}
276277func (k *Knots) delete(w http.ResponseWriter, r *http.Request) {
278- user := k.OAuth.GetUser(r)
279 l := k.Logger.With("handler", "delete")
280281 noticeId := "operation-error"
···294 // get record from db first
295 registrations, err := db.GetRegistrations(
296 k.Db,
297- orm.FilterEq("did", user.Did),
298 orm.FilterEq("domain", domain),
299 )
300 if err != nil {
···322323 err = db.DeleteKnot(
324 tx,
325- orm.FilterEq("did", user.Did),
326 orm.FilterEq("domain", domain),
327 )
328 if err != nil {
···350351 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
352 Collection: tangled.KnotNSID,
353- Repo: user.Did,
354 Rkey: domain,
355 })
356 if err != nil {
···382}
383384func (k *Knots) retry(w http.ResponseWriter, r *http.Request) {
385- user := k.OAuth.GetUser(r)
386 l := k.Logger.With("handler", "retry")
387388 noticeId := "operation-error"
···398 return
399 }
400 l = l.With("domain", domain)
401- l = l.With("user", user.Did)
402403 // get record from db first
404 registrations, err := db.GetRegistrations(
405 k.Db,
406- orm.FilterEq("did", user.Did),
407 orm.FilterEq("domain", domain),
408 )
409 if err != nil {
···419 registration := registrations[0]
420421 // begin verification
422- err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
423 if err != nil {
424 l.Error("verification failed", "err", err)
425···437 return
438 }
439440- err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
441 if err != nil {
442 l.Error("failed to mark verified", "err", err)
443 k.Pages.Notice(w, noticeId, err.Error())
···456 return
457 }
458459- ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Did, domain)
460 var exCid *string
461 if ex != nil {
462 exCid = ex.Cid
···465 // ignore the error here
466 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
467 Collection: tangled.KnotNSID,
468- Repo: user.Did,
469 Rkey: domain,
470 Record: &lexutil.LexiconTypeDecoder{
471 Val: &tangled.Knot{
···494 // Get updated registration to show
495 registrations, err = db.GetRegistrations(
496 k.Db,
497- orm.FilterEq("did", user.Did),
498 orm.FilterEq("domain", domain),
499 )
500 if err != nil {
···516}
517518func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
519- user := k.OAuth.GetUser(r)
520 l := k.Logger.With("handler", "addMember")
521522 domain := chi.URLParam(r, "domain")
···526 return
527 }
528 l = l.With("domain", domain)
529- l = l.With("user", user.Did)
530531 registrations, err := db.GetRegistrations(
532 k.Db,
533- orm.FilterEq("did", user.Did),
534 orm.FilterEq("domain", domain),
535 orm.FilterIsNot("registered", "null"),
536 )
···583584 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
585 Collection: tangled.KnotMemberNSID,
586- Repo: user.Did,
587 Rkey: rkey,
588 Record: &lexutil.LexiconTypeDecoder{
589 Val: &tangled.KnotMember{
···618}
619620func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
621- user := k.OAuth.GetUser(r)
622 l := k.Logger.With("handler", "removeMember")
623624 noticeId := "operation-error"
···634 return
635 }
636 l = l.With("domain", domain)
637- l = l.With("user", user.Did)
638639 registrations, err := db.GetRegistrations(
640 k.Db,
641- orm.FilterEq("did", user.Did),
642 orm.FilterEq("domain", domain),
643 orm.FilterIsNot("registered", "null"),
644 )
···70}
7172func (k *Knots) knots(w http.ResponseWriter, r *http.Request) {
73+ user := k.OAuth.GetMultiAccountUser(r)
74 registrations, err := db.GetRegistrations(
75 k.Db,
76+ orm.FilterEq("did", user.Active.Did),
77 )
78 if err != nil {
79 k.Logger.Error("failed to fetch knot registrations", "err", err)
···92func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
93 l := k.Logger.With("handler", "dashboard")
9495+ user := k.OAuth.GetMultiAccountUser(r)
96+ l = l.With("user", user.Active.Did)
9798 domain := chi.URLParam(r, "domain")
99 if domain == "" {
···103104 registrations, err := db.GetRegistrations(
105 k.Db,
106+ orm.FilterEq("did", user.Active.Did),
107 orm.FilterEq("domain", domain),
108 )
109 if err != nil {
···154}
155156func (k *Knots) register(w http.ResponseWriter, r *http.Request) {
157+ user := k.OAuth.GetMultiAccountUser(r)
158 l := k.Logger.With("handler", "register")
159160 noticeId := "register-error"
···175 return
176 }
177 l = l.With("domain", domain)
178+ l = l.With("user", user.Active.Did)
179180 tx, err := k.Db.Begin()
181 if err != nil {
···188 k.Enforcer.E.LoadPolicy()
189 }()
190191+ err = db.AddKnot(tx, domain, user.Active.Did)
192 if err != nil {
193 l.Error("failed to insert", "err", err)
194 fail()
···210 return
211 }
212213+ ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Active.Did, domain)
214 var exCid *string
215 if ex != nil {
216 exCid = ex.Cid
···219 // re-announce by registering under same rkey
220 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
221 Collection: tangled.KnotNSID,
222+ Repo: user.Active.Did,
223 Rkey: domain,
224 Record: &lexutil.LexiconTypeDecoder{
225 Val: &tangled.Knot{
···250 }
251252 // begin verification
253+ err = serververify.RunVerification(r.Context(), domain, user.Active.Did, k.Config.Core.Dev)
254 if err != nil {
255 l.Error("verification failed", "err", err)
256 k.Pages.HxRefresh(w)
257 return
258 }
259260+ err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Active.Did)
261 if err != nil {
262 l.Error("failed to mark verified", "err", err)
263 k.Pages.HxRefresh(w)
···275}
276277func (k *Knots) delete(w http.ResponseWriter, r *http.Request) {
278+ user := k.OAuth.GetMultiAccountUser(r)
279 l := k.Logger.With("handler", "delete")
280281 noticeId := "operation-error"
···294 // get record from db first
295 registrations, err := db.GetRegistrations(
296 k.Db,
297+ orm.FilterEq("did", user.Active.Did),
298 orm.FilterEq("domain", domain),
299 )
300 if err != nil {
···322323 err = db.DeleteKnot(
324 tx,
325+ orm.FilterEq("did", user.Active.Did),
326 orm.FilterEq("domain", domain),
327 )
328 if err != nil {
···350351 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
352 Collection: tangled.KnotNSID,
353+ Repo: user.Active.Did,
354 Rkey: domain,
355 })
356 if err != nil {
···382}
383384func (k *Knots) retry(w http.ResponseWriter, r *http.Request) {
385+ user := k.OAuth.GetMultiAccountUser(r)
386 l := k.Logger.With("handler", "retry")
387388 noticeId := "operation-error"
···398 return
399 }
400 l = l.With("domain", domain)
401+ l = l.With("user", user.Active.Did)
402403 // get record from db first
404 registrations, err := db.GetRegistrations(
405 k.Db,
406+ orm.FilterEq("did", user.Active.Did),
407 orm.FilterEq("domain", domain),
408 )
409 if err != nil {
···419 registration := registrations[0]
420421 // begin verification
422+ err = serververify.RunVerification(r.Context(), domain, user.Active.Did, k.Config.Core.Dev)
423 if err != nil {
424 l.Error("verification failed", "err", err)
425···437 return
438 }
439440+ err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Active.Did)
441 if err != nil {
442 l.Error("failed to mark verified", "err", err)
443 k.Pages.Notice(w, noticeId, err.Error())
···456 return
457 }
458459+ ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Active.Did, domain)
460 var exCid *string
461 if ex != nil {
462 exCid = ex.Cid
···465 // ignore the error here
466 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
467 Collection: tangled.KnotNSID,
468+ Repo: user.Active.Did,
469 Rkey: domain,
470 Record: &lexutil.LexiconTypeDecoder{
471 Val: &tangled.Knot{
···494 // Get updated registration to show
495 registrations, err = db.GetRegistrations(
496 k.Db,
497+ orm.FilterEq("did", user.Active.Did),
498 orm.FilterEq("domain", domain),
499 )
500 if err != nil {
···516}
517518func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
519+ user := k.OAuth.GetMultiAccountUser(r)
520 l := k.Logger.With("handler", "addMember")
521522 domain := chi.URLParam(r, "domain")
···526 return
527 }
528 l = l.With("domain", domain)
529+ l = l.With("user", user.Active.Did)
530531 registrations, err := db.GetRegistrations(
532 k.Db,
533+ orm.FilterEq("did", user.Active.Did),
534 orm.FilterEq("domain", domain),
535 orm.FilterIsNot("registered", "null"),
536 )
···583584 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
585 Collection: tangled.KnotMemberNSID,
586+ Repo: user.Active.Did,
587 Rkey: rkey,
588 Record: &lexutil.LexiconTypeDecoder{
589 Val: &tangled.KnotMember{
···618}
619620func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
621+ user := k.OAuth.GetMultiAccountUser(r)
622 l := k.Logger.With("handler", "removeMember")
623624 noticeId := "operation-error"
···634 return
635 }
636 l = l.With("domain", domain)
637+ l = l.With("user", user.Active.Did)
638639 registrations, err := db.GetRegistrations(
640 k.Db,
641+ orm.FilterEq("did", user.Active.Did),
642 orm.FilterEq("domain", domain),
643 orm.FilterIsNot("registered", "null"),
644 )
+2-2
appview/labels/labels.go
···68// - this handler should calculate the diff in order to create the labelop record
69// - we need the diff in order to maintain a "history" of operations performed by users
70func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) {
71- user := l.oauth.GetUser(r)
7273 noticeId := "add-label-error"
74···82 return
83 }
8485- did := user.Did
86 rkey := tid.TID()
87 performedAt := time.Now()
88 indexedAt := time.Now()
···68// - this handler should calculate the diff in order to create the labelop record
69// - we need the diff in order to maintain a "history" of operations performed by users
70func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) {
71+ user := l.oauth.GetMultiAccountUser(r)
7273 noticeId := "add-label-error"
74···82 return
83 }
8485+ did := user.Active.Did
86 rkey := tid.TID()
87 performedAt := time.Now()
88 indexedAt := time.Now()
+6-8
appview/middleware/middleware.go
···115 return func(next http.Handler) http.Handler {
116 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117 // requires auth also
118- actor := mw.oauth.GetUser(r)
119 if actor == nil {
120 // we need a logged in user
121 log.Printf("not logged in, redirecting")
···128 return
129 }
130131- ok, err := mw.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
132 if err != nil || !ok {
133- // we need a logged in user
134- log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
135 http.Error(w, "Forbiden", http.StatusUnauthorized)
136 return
137 }
···149 return func(next http.Handler) http.Handler {
150 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
151 // requires auth also
152- actor := mw.oauth.GetUser(r)
153 if actor == nil {
154 // we need a logged in user
155 log.Printf("not logged in, redirecting")
···162 return
163 }
164165- ok, err := mw.enforcer.E.Enforce(actor.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
166 if err != nil || !ok {
167- // we need a logged in user
168- log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.DidSlashRepo())
169 http.Error(w, "Forbiden", http.StatusUnauthorized)
170 return
171 }
···115 return func(next http.Handler) http.Handler {
116 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117 // requires auth also
118+ actor := mw.oauth.GetMultiAccountUser(r)
119 if actor == nil {
120 // we need a logged in user
121 log.Printf("not logged in, redirecting")
···128 return
129 }
130131+ ok, err := mw.enforcer.E.HasGroupingPolicy(actor.Active.Did, group, domain)
132 if err != nil || !ok {
133+ log.Printf("%s does not have perms of a %s in domain %s", actor.Active.Did, group, domain)
0134 http.Error(w, "Forbiden", http.StatusUnauthorized)
135 return
136 }
···148 return func(next http.Handler) http.Handler {
149 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
150 // requires auth also
151+ actor := mw.oauth.GetMultiAccountUser(r)
152 if actor == nil {
153 // we need a logged in user
154 log.Printf("not logged in, redirecting")
···161 return
162 }
163164+ ok, err := mw.enforcer.E.Enforce(actor.Active.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
165 if err != nil || !ok {
166+ log.Printf("%s does not have perms of a %s in repo %s", actor.Active.Did, requiredPerm, f.DidSlashRepo())
0167 http.Error(w, "Forbiden", http.StatusUnauthorized)
168 return
169 }
+48
appview/models/pipeline.go
···1package models
23import (
04 "slices"
05 "time"
67 "github.com/bluesky-social/indigo/atproto/syntax"
8 "github.com/go-git/go-git/v5/plumbing"
09 spindle "tangled.org/core/spindle/models"
10 "tangled.org/core/workflow"
11)
···25 Statuses map[string]WorkflowStatus
26}
27000028type WorkflowStatus struct {
29 Data []PipelineStatus
30}
···52 return 0
53}
54000000000000000000000000000000000000055func (p Pipeline) Counts() map[string]int {
56 m := make(map[string]int)
57 for _, w := range p.Statuses {
···128 Error *string
129 ExitCode int
130}
0000
···1package models
23import (
4+ "fmt"
5 "slices"
6+ "strings"
7 "time"
89 "github.com/bluesky-social/indigo/atproto/syntax"
10 "github.com/go-git/go-git/v5/plumbing"
11+ "tangled.org/core/api/tangled"
12 spindle "tangled.org/core/spindle/models"
13 "tangled.org/core/workflow"
14)
···28 Statuses map[string]WorkflowStatus
29}
3031+func (p *Pipeline) AtUri() syntax.ATURI {
32+ return syntax.ATURI(fmt.Sprintf("at://did:web:%s/%s/%s", p.Knot, tangled.PipelineNSID, p.Rkey))
33+}
34+35type WorkflowStatus struct {
36 Data []PipelineStatus
37}
···59 return 0
60}
6162+// produces short summary of successes:
63+// - "0/4" when zero successes of 4 workflows
64+// - "4/4" when all successes of 4 workflows
65+// - "0/0" when no workflows run in this pipeline
66+func (p Pipeline) ShortStatusSummary() string {
67+ counts := make(map[spindle.StatusKind]int)
68+ for _, w := range p.Statuses {
69+ counts[w.Latest().Status] += 1
70+ }
71+72+ total := len(p.Statuses)
73+ successes := counts[spindle.StatusKindSuccess]
74+75+ return fmt.Sprintf("%d/%d", successes, total)
76+}
77+78+// produces a string of the form "3/4 success, 2/4 failed, 1/4 pending"
79+func (p Pipeline) LongStatusSummary() string {
80+ counts := make(map[spindle.StatusKind]int)
81+ for _, w := range p.Statuses {
82+ counts[w.Latest().Status] += 1
83+ }
84+85+ total := len(p.Statuses)
86+87+ var result []string
88+ // finish states first, followed by start states
89+ states := append(spindle.FinishStates[:], spindle.StartStates[:]...)
90+ for _, state := range states {
91+ if count, ok := counts[state]; ok {
92+ result = append(result, fmt.Sprintf("%d/%d %s", count, total, state.String()))
93+ }
94+ }
95+96+ return strings.Join(result, ", ")
97+}
98+99func (p Pipeline) Counts() map[string]int {
100 m := make(map[string]int)
101 for _, w := range p.Statuses {
···172 Error *string
173 ExitCode int
174}
175+176+func (ps *PipelineStatus) PipelineAt() syntax.ATURI {
177+ return syntax.ATURI(fmt.Sprintf("at://did:web:%s/%s/%s", ps.PipelineKnot, tangled.PipelineNSID, ps.PipelineRkey))
178+}
+7-17
appview/models/pull.go
···171 return syntax.ATURI(p.CommentAt)
172}
173174-// func (p *PullComment) AsRecord() tangled.RepoPullComment {
175-// mentions := make([]string, len(p.Mentions))
176-// for i, did := range p.Mentions {
177-// mentions[i] = string(did)
178-// }
179-// references := make([]string, len(p.References))
180-// for i, uri := range p.References {
181-// references[i] = string(uri)
182-// }
183-// return tangled.RepoPullComment{
184-// Pull: p.PullAt,
185-// Body: p.Body,
186-// Mentions: mentions,
187-// References: references,
188-// CreatedAt: p.Created.Format(time.RFC3339),
189-// }
190-// }
191192func (p *Pull) LastRoundNumber() int {
193 return len(p.Submissions) - 1
···171 return syntax.ATURI(p.CommentAt)
172}
173174+func (p *Pull) TotalComments() int {
175+ total := 0
176+ for _, s := range p.Submissions {
177+ total += len(s.Comments)
178+ }
179+ return total
180+}
0000000000181182func (p *Pull) LastRoundNumber() int {
183 return len(p.Submissions) - 1
+7-6
appview/notifications/notifications.go
···4849func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) {
50 l := n.logger.With("handler", "notificationsPage")
51- user := n.oauth.GetUser(r)
5253 page := pagination.FromContext(r.Context())
5455 total, err := db.CountNotifications(
56 n.db,
57- orm.FilterEq("recipient_did", user.Did),
58 )
59 if err != nil {
60 l.Error("failed to get total notifications", "err", err)
···65 notifications, err := db.GetNotificationsWithEntities(
66 n.db,
67 page,
68- orm.FilterEq("recipient_did", user.Did),
69 )
70 if err != nil {
71 l.Error("failed to get notifications", "err", err)
···73 return
74 }
7576- err = db.MarkAllNotificationsRead(n.db, user.Did)
77 if err != nil {
78 l.Error("failed to mark notifications as read", "err", err)
79 }
···90}
9192func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
93- user := n.oauth.GetUser(r)
94 if user == nil {
095 return
96 }
9798 count, err := db.CountNotifications(
99 n.db,
100- orm.FilterEq("recipient_did", user.Did),
101 orm.FilterEq("read", 0),
102 )
103 if err != nil {
···4849func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) {
50 l := n.logger.With("handler", "notificationsPage")
51+ user := n.oauth.GetMultiAccountUser(r)
5253 page := pagination.FromContext(r.Context())
5455 total, err := db.CountNotifications(
56 n.db,
57+ orm.FilterEq("recipient_did", user.Active.Did),
58 )
59 if err != nil {
60 l.Error("failed to get total notifications", "err", err)
···65 notifications, err := db.GetNotificationsWithEntities(
66 n.db,
67 page,
68+ orm.FilterEq("recipient_did", user.Active.Did),
69 )
70 if err != nil {
71 l.Error("failed to get notifications", "err", err)
···73 return
74 }
7576+ err = db.MarkAllNotificationsRead(n.db, user.Active.Did)
77 if err != nil {
78 l.Error("failed to mark notifications as read", "err", err)
79 }
···90}
9192func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
93+ user := n.oauth.GetMultiAccountUser(r)
94 if user == nil {
95+ http.Error(w, "Forbidden", http.StatusUnauthorized)
96 return
97 }
9899 count, err := db.CountNotifications(
100 n.db,
101+ orm.FilterEq("recipient_did", user.Active.Did),
102 orm.FilterEq("read", 0),
103 )
104 if err != nil {
···1package types
23import (
004 "github.com/bluekeyes/go-gitdiff/gitdiff"
05)
67type DiffOpts struct {
8 Split bool `json:"split"`
9}
1011-type TextFragment struct {
12- Header string `json:"comment"`
13- Lines []gitdiff.Line `json:"lines"`
00000000000014}
1516type Diff struct {
···26 IsRename bool `json:"is_rename"`
27}
2829-type DiffStat struct {
30- Insertions int64
31- Deletions int64
32-}
33-34-func (d *Diff) Stats() DiffStat {
35- var stats DiffStat
36 for _, f := range d.TextFragments {
37 stats.Insertions += f.LinesAdded
38 stats.Deletions += f.LinesDeleted
···40 return stats
41}
4243-// A nicer git diff representation.
44-type NiceDiff struct {
45- Commit Commit `json:"commit"`
46- Stat struct {
47- FilesChanged int `json:"files_changed"`
48- Insertions int `json:"insertions"`
49- Deletions int `json:"deletions"`
50- } `json:"stat"`
51- Diff []Diff `json:"diff"`
52}
5354type DiffTree struct {
···58 Diff []*gitdiff.File `json:"diff"`
59}
6061-func (d *NiceDiff) ChangedFiles() []string {
62- files := make([]string, len(d.Diff))
006364- for i, f := range d.Diff {
65- if f.IsDelete {
66- files[i] = f.Name.Old
0000000000067 } else {
68- files[i] = f.Name.New
69 }
70 }
007172- return files
073}
7475-// used by html elements as a unique ID for hrefs
76-func (d *Diff) Id() string {
77 if d.IsDelete {
78 return d.Name.Old
79 }
80 return d.Name.New
81}
8283-func (d *Diff) Split() *SplitDiff {
0000000000000000000000084 fragments := make([]SplitFragment, len(d.TextFragments))
85 for i, fragment := range d.TextFragments {
86 leftLines, rightLines := SeparateLines(&fragment)
···91 }
92 }
9394- return &SplitDiff{
95 Name: d.Id(),
96 TextFragments: fragments,
97 }
···1package types
23import (
4+ "net/url"
5+6 "github.com/bluekeyes/go-gitdiff/gitdiff"
7+ "tangled.org/core/appview/filetree"
8)
910type DiffOpts struct {
11 Split bool `json:"split"`
12}
1314+func (d DiffOpts) Encode() string {
15+ values := make(url.Values)
16+ if d.Split {
17+ values.Set("diff", "split")
18+ } else {
19+ values.Set("diff", "unified")
20+ }
21+ return values.Encode()
22+}
23+24+// A nicer git diff representation.
25+type NiceDiff struct {
26+ Commit Commit `json:"commit"`
27+ Stat DiffStat `json:"stat"`
28+ Diff []Diff `json:"diff"`
29}
3031type Diff struct {
···41 IsRename bool `json:"is_rename"`
42}
4344+func (d Diff) Stats() DiffFileStat {
45+ var stats DiffFileStat
0000046 for _, f := range d.TextFragments {
47 stats.Insertions += f.LinesAdded
48 stats.Deletions += f.LinesDeleted
···50 return stats
51}
5253+type DiffStat struct {
54+ Insertions int64 `json:"insertions"`
55+ Deletions int64 `json:"deletions"`
56+ FilesChanged int `json:"files_changed"`
57+}
58+59+type DiffFileStat struct {
60+ Insertions int64
61+ Deletions int64
62}
6364type DiffTree struct {
···68 Diff []*gitdiff.File `json:"diff"`
69}
7071+type DiffFileName struct {
72+ Old string
73+ New string
74+}
7576+func (d NiceDiff) ChangedFiles() []DiffFileRenderer {
77+ drs := make([]DiffFileRenderer, len(d.Diff))
78+ for i, s := range d.Diff {
79+ drs[i] = s
80+ }
81+ return drs
82+}
83+84+func (d NiceDiff) FileTree() *filetree.FileTreeNode {
85+ fs := make([]string, len(d.Diff))
86+ for i, s := range d.Diff {
87+ n := s.Names()
88+ if n.New == "" {
89+ fs[i] = n.Old
90 } else {
91+ fs[i] = n.New
92 }
93 }
94+ return filetree.FileTree(fs)
95+}
9697+func (d NiceDiff) Stats() DiffStat {
98+ return d.Stat
99}
100101+func (d Diff) Id() string {
0102 if d.IsDelete {
103 return d.Name.Old
104 }
105 return d.Name.New
106}
107108+func (d Diff) Names() DiffFileName {
109+ var n DiffFileName
110+ if d.IsDelete {
111+ n.Old = d.Name.Old
112+ return n
113+ } else if d.IsCopy || d.IsRename {
114+ n.Old = d.Name.Old
115+ n.New = d.Name.New
116+ return n
117+ } else {
118+ n.New = d.Name.New
119+ return n
120+ }
121+}
122+123+func (d Diff) CanRender() string {
124+ if d.IsBinary {
125+ return "This is a binary file and will not be displayed."
126+ }
127+128+ return ""
129+}
130+131+func (d Diff) Split() SplitDiff {
132 fragments := make([]SplitFragment, len(d.TextFragments))
133 for i, fragment := range d.TextFragments {
134 leftLines, rightLines := SeparateLines(&fragment)
···139 }
140 }
141142+ return SplitDiff{
143 Name: d.Id(),
144 TextFragments: fragments,
145 }
+31
types/diff_renderer.go
···0000000000000000000000000000000
···1+package types
2+3+import "tangled.org/core/appview/filetree"
4+5+type DiffRenderer interface {
6+ // list of file affected by these diffs
7+ ChangedFiles() []DiffFileRenderer
8+9+ // filetree
10+ FileTree() *filetree.FileTreeNode
11+12+ Stats() DiffStat
13+}
14+15+type DiffFileRenderer interface {
16+ // html ID for each file in the diff
17+ Id() string
18+19+ // produce a splitdiff
20+ Split() SplitDiff
21+22+ // stats for this single file
23+ Stats() DiffFileStat
24+25+ // old and new name of file
26+ Names() DiffFileName
27+28+ // whether this diff can be displayed,
29+ // returns a reason if not, and the empty string if it can
30+ CanRender() string
31+}
+11-2
types/diff_test.go
···1package types
23-import "testing"
0045func TestDiffId(t *testing.T) {
6 tests := []struct {
···105 }
106107 for i, diff := range nd.Diff {
108- if changedFiles[i] != diff.Id() {
109 t.Errorf("ChangedFiles()[%d] = %q, but Diff.Id() = %q", i, changedFiles[i], diff.Id())
110 }
111 }
112}
0000000
···22 TextFragments []SplitFragment `json:"fragments"`
23}
2425-// used by html elements as a unique ID for hrefs
26-func (d *SplitDiff) Id() string {
27 return d.Name
28}
29