···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,
···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,
+31-31
appview/knots/knots.go
···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 )
···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 )
+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.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()
···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()
+8-6
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.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 }
···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 }
-48
appview/models/pipeline.go
···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-}
···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
+17-7
appview/models/pull.go
···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
···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
+6-7
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.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 {
···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 {
···30 <div class="mx-6">
31 These services may not be fully accessible until upgraded.
32 <a class="underline text-red-800 dark:text-red-200"
33- href="https://docs.tangled.org/migrating-knots-and-spindles.html">
34 Click to read the upgrade guide</a>.
35 </div>
36 </details>
···30 <div class="mx-6">
31 These services may not be fully accessible until upgraded.
32 <a class="underline text-red-800 dark:text-red-200"
33+ href="https://docs.tangled.org/migrating-knots-spindles.html#migrating-knots-spindles">
34 Click to read the upgrade guide</a>.
35 </div>
36 </details>
···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-}
75-76-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-}
8384-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 }
···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))
00000000006364+ for i, f := range d.Diff {
65+ if f.IsDelete {
66+ files[i] = f.Name.Old
00067 } 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 }
-31
types/diff_renderer.go
···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-}
···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