Monorepo for Tangled tangled.org

draft: appview: service layer #800

open opened by boltless.me targeting master from sl/uvpzuszrulvq

Obviously file naming of appview/web/handler/*.go files are directly against to go convention. Though I think flattening all handler files can significantly reduce the effort involved in file naming and structuring. We are already grouping core services by domains, and doing same for web handers is just over-complicating.

Signed-off-by: Seongmin Lee git@boltless.me

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3m5jyyj76xa22
+96 -140
Interdiff #10 #11
appview/oauth/handler.go

This patch was likely rebased, as context lines do not match.

-10
appview/oauth/session.go
··· 1 - package oauth 2 - 3 - import ( 4 - "net/http" 5 - 6 - "github.com/bluesky-social/indigo/atproto/auth/oauth" 7 - ) 8 - 9 - func (o *OAuth) SaveSession2(w http.ResponseWriter, r *http.Request, sessData *oauth.ClientSessionData) { 10 - }
appview/service/issue/errors.go

This file has not been changed.

+21 -21
appview/service/issue/issue.go
··· 61 61 62 62 func (s *Service) NewIssue(ctx context.Context, repo *models.Repo, title, body string) (*models.Issue, error) { 63 63 l := s.logger.With("method", "NewIssue") 64 - sess := session.FromContext(ctx) 65 - if sess == nil { 64 + sess, ok := session.FromContext(ctx) 65 + if !ok { 66 66 l.Error("user session is missing in context") 67 67 return nil, ErrForbidden 68 68 } 69 - authorDid := sess.Data.AccountDID 69 + authorDid := syntax.DID(sess.User.Did) 70 + atpclient := sess.AtpClient 70 71 l = l.With("did", authorDid) 71 72 72 73 mentions, references := s.refResolver.Resolve(ctx, body) 73 74 74 75 issue := models.Issue{ 75 - RepoAt: repo.RepoAt(), 76 + Did: authorDid.String(), 76 77 Rkey: tid.TID(), 78 + RepoAt: repo.RepoAt(), 77 79 Title: title, 78 80 Body: body, 79 - Open: true, 80 - Did: authorDid.String(), 81 81 Created: time.Now(), 82 82 Mentions: mentions, 83 83 References: references, 84 + Open: true, 84 85 Repo: repo, 85 86 } 86 87 ··· 101 102 return nil, ErrDatabaseFail 102 103 } 103 104 104 - atpclient := sess.APIClient() 105 105 record := issue.AsRecord() 106 106 _, err = atproto.RepoPutRecord(ctx, atpclient, &atproto.RepoPutRecord_Input{ 107 - Repo: authorDid.String(), 107 + Repo: issue.Did, 108 108 Collection: tangled.RepoIssueNSID, 109 109 Rkey: issue.Rkey, 110 110 Record: &lexutil.LexiconTypeDecoder{ ··· 163 163 164 164 func (s *Service) EditIssue(ctx context.Context, issue *models.Issue) error { 165 165 l := s.logger.With("method", "EditIssue") 166 - sess := session.FromContext(ctx) 167 - if sess == nil { 166 + sess, ok := session.FromContext(ctx) 167 + if !ok { 168 168 l.Error("user session is missing in context") 169 169 return ErrForbidden 170 170 } 171 - sessDid := sess.Data.AccountDID 172 - l = l.With("did", sessDid) 171 + atpclient := sess.AtpClient 172 + l = l.With("did", sess.User.Did) 173 173 174 174 mentions, references := s.refResolver.Resolve(ctx, issue.Body) 175 175 issue.Mentions = mentions 176 176 issue.References = references 177 177 178 - if sessDid != syntax.DID(issue.Did) { 178 + if sess.User.Did != issue.Did { 179 179 l.Error("only author can edit the issue") 180 180 return ErrForbidden 181 181 } ··· 197 197 return ErrDatabaseFail 198 198 } 199 199 200 - atpclient := sess.APIClient() 201 200 record := issue.AsRecord() 202 201 203 202 ex, err := atproto.RepoGetRecord(ctx, atpclient, "", tangled.RepoIssueNSID, issue.Did, issue.Rkey) ··· 206 205 return ErrPDSFail 207 206 } 208 207 _, err = atproto.RepoPutRecord(ctx, atpclient, &atproto.RepoPutRecord_Input{ 208 + Repo: issue.Did, 209 209 Collection: tangled.RepoIssueNSID, 210 + Rkey: issue.Rkey, 210 211 SwapRecord: ex.Cid, 211 212 Record: &lexutil.LexiconTypeDecoder{ 212 213 Val: &record, ··· 222 223 return ErrDatabaseFail 223 224 } 224 225 225 - // TODO: notify PutIssue 226 + // TODO: notify EditIssue 226 227 227 228 return nil 228 229 } 229 230 230 231 func (s *Service) DeleteIssue(ctx context.Context, issue *models.Issue) error { 231 232 l := s.logger.With("method", "DeleteIssue") 232 - sess := session.FromContext(ctx) 233 - if sess == nil { 233 + sess, ok := session.FromContext(ctx) 234 + if !ok { 234 235 l.Error("user session is missing in context") 235 236 return ErrForbidden 236 237 } 237 - sessDid := sess.Data.AccountDID 238 - l = l.With("did", sessDid) 238 + atpclient := sess.AtpClient 239 + l = l.With("did", sess.User.Did) 239 240 240 - if sessDid != syntax.DID(issue.Did) { 241 + if sess.User.Did != issue.Did { 241 242 l.Error("only author can edit the issue") 242 243 return ErrForbidden 243 244 } ··· 254 255 return ErrDatabaseFail 255 256 } 256 257 257 - atpclient := sess.APIClient() 258 258 _, err = atproto.RepoDeleteRecord(ctx, atpclient, &atproto.RepoDeleteRecord_Input{ 259 259 Collection: tangled.RepoIssueNSID, 260 260 Repo: issue.Did,
+6 -6
appview/service/issue/state.go
··· 13 13 14 14 func (s *Service) CloseIssue(ctx context.Context, issue *models.Issue) error { 15 15 l := s.logger.With("method", "CloseIssue") 16 - sess := session.FromContext(ctx) 17 - if sess == nil { 16 + sess, ok := session.FromContext(ctx) 17 + if !ok { 18 18 l.Error("user session is missing in context") 19 19 return ErrUnAuthenticated 20 20 } 21 - sessDid := sess.Data.AccountDID 21 + sessDid := syntax.DID(sess.User.Did) 22 22 l = l.With("did", sessDid) 23 23 24 24 // TODO: make this more granular ··· 49 49 50 50 func (s *Service) ReopenIssue(ctx context.Context, issue *models.Issue) error { 51 51 l := s.logger.With("method", "ReopenIssue") 52 - sess := session.FromContext(ctx) 53 - if sess == nil { 52 + sess, ok := session.FromContext(ctx) 53 + if !ok { 54 54 l.Error("user session is missing in context") 55 55 return ErrUnAuthenticated 56 56 } 57 - sessDid := sess.Data.AccountDID 57 + sessDid := syntax.DID(sess.User.Did) 58 58 l = l.With("did", sessDid) 59 59 60 60 // TODO: make this more granular
appview/service/repo/errors.go

This file has not been changed.

+12 -7
appview/service/repo/repo.go
··· 6 6 "time" 7 7 8 8 "github.com/bluesky-social/indigo/api/atproto" 9 + lexutil "github.com/bluesky-social/indigo/lex/util" 9 10 "tangled.org/core/api/tangled" 10 11 "tangled.org/core/appview/config" 11 12 "tangled.org/core/appview/db" ··· 40 41 // It expects atproto session to be passed in `ctx` 41 42 func (s *Service) NewRepo(ctx context.Context, name, description, knot string) (*models.Repo, error) { 42 43 l := s.logger.With("method", "NewRepo") 43 - sess := session.FromContext(ctx) 44 - if sess == nil { 44 + sess, ok := session.FromContext(ctx) 45 + if !ok { 45 46 l.Error("user session is missing in context") 46 47 return nil, ErrForbidden 47 48 } 48 49 49 - ownerDid := sess.Data.AccountDID 50 - l = l.With("did", ownerDid) 50 + atpclient := sess.AtpClient 51 + l = l.With("did", sess.User.Did) 51 52 52 53 repo := models.Repo{ 53 - Did: ownerDid.String(), 54 + Did: sess.User.Did, 54 55 Name: name, 55 56 Knot: knot, 56 57 Rkey: tid.TID(), ··· 72 73 return nil, ErrDatabaseFail 73 74 } 74 75 75 - atpclient := sess.APIClient() 76 + record := repo.AsRecord() 76 77 _, err = atproto.RepoPutRecord(ctx, atpclient, &atproto.RepoPutRecord_Input{ 77 - Collection: tangled.RepoNSID, 78 78 Repo: repo.Did, 79 + Collection: tangled.RepoNSID, 80 + Rkey: repo.Rkey, 81 + Record: &lexutil.LexiconTypeDecoder{ 82 + Val: &record, 83 + }, 79 84 }) 80 85 if err != nil { 81 86 l.Error("atproto.RepoPutRecord failed", "err", err)
+18 -19
appview/service/repo/repoinfo.go
··· 6 6 "github.com/bluesky-social/indigo/atproto/identity" 7 7 "tangled.org/core/appview/db" 8 8 "tangled.org/core/appview/models" 9 - "tangled.org/core/appview/oauth" 10 9 "tangled.org/core/appview/pages/repoinfo" 10 + "tangled.org/core/appview/session" 11 11 ) 12 12 13 - // GetRepoInfo converts given `Repo` to `RepoInfo` object. 14 - // The `user` can be nil. 15 - // NOTE: RepoInfo is bad design and should be removed in future. 16 - // avoid using this method if you can. 17 - func (s *Service) GetRepoInfo( 13 + // MakeRepoInfo constructs [repoinfo.RepoInfo] object from given [models.Repo]. 14 + // 15 + // NOTE: [repoinfo.RepoInfo] is bad design and should be removed in future. 16 + // Avoid using this method if you can. 17 + func (s *Service) MakeRepoInfo( 18 18 ctx context.Context, 19 19 ownerId *identity.Identity, 20 20 baseRepo *models.Repo, 21 21 currentDir, ref string, 22 - user *oauth.User, 23 - ) (*repoinfo.RepoInfo, error) { 22 + ) repoinfo.RepoInfo { 24 23 var ( 25 24 repoAt = baseRepo.RepoAt() 26 25 isStarred = false 27 26 roles = repoinfo.RolesInRepo{} 27 + l = s.logger.With("method", "MakeRepoInfo").With("repoAt", repoAt) 28 28 ) 29 - if user != nil { 30 - isStarred = db.GetStarStatus(s.db, user.Did, repoAt) 31 - roles.Roles = s.enforcer.GetPermissionsInRepo(user.Did, baseRepo.Knot, baseRepo.DidSlashRepo()) 29 + sess, ok := session.FromContext(ctx) 30 + if ok { 31 + isStarred = db.GetStarStatus(s.db, sess.User.Did, repoAt) 32 + roles.Roles = s.enforcer.GetPermissionsInRepo(sess.User.Did, baseRepo.Knot, baseRepo.DidSlashRepo()) 32 33 } 33 34 34 35 stats := baseRepo.RepoStats 35 36 if stats == nil { 36 37 starCount, err := db.GetStarCount(s.db, repoAt) 37 38 if err != nil { 38 - return nil, err 39 + l.Error("failed to get star count", "err", err) 39 40 } 40 41 issueCount, err := db.GetIssueCount(s.db, repoAt) 41 42 if err != nil { 42 - return nil, err 43 + l.Error("failed to get issue count", "err", err) 43 44 } 44 45 pullCount, err := db.GetPullCount(s.db, repoAt) 45 46 if err != nil { 46 - return nil, err 47 + l.Error("failed to get pull count", "err", err) 47 48 } 48 49 stats = &models.RepoStats{ 49 50 StarCount: starCount, ··· 57 58 if baseRepo.Source != "" { 58 59 sourceRepo, err = db.GetRepoByAtUri(s.db, baseRepo.Source) 59 60 if err != nil { 60 - return nil, err 61 + l.Error("failed to get source repo", "source", baseRepo.Source, "err", err) 61 62 } 62 63 } 63 64 64 - repoInfo := &repoinfo.RepoInfo{ 65 - // ok this is basically a models.Repo 65 + return repoinfo.RepoInfo{ 66 + // this is basically a models.Repo 66 67 OwnerDid: baseRepo.Did, 67 68 OwnerHandle: ownerId.Handle.String(), // TODO: shouldn't use 68 69 Name: baseRepo.Name, ··· 85 86 IsStarred: isStarred, 86 87 Roles: roles, 87 88 } 88 - 89 - return repoInfo, nil 90 89 }
+8 -10
appview/session/context.go
··· 3 3 import ( 4 4 "context" 5 5 6 - toauth "tangled.org/core/appview/oauth" 6 + "tangled.org/core/appview/oauth" 7 7 ) 8 8 9 9 type ctxKey struct{} ··· 12 12 return context.WithValue(ctx, ctxKey{}, &sess) 13 13 } 14 14 15 - func FromContext(ctx context.Context) *Session { 15 + func FromContext(ctx context.Context) (*Session, bool) { 16 16 sess, ok := ctx.Value(ctxKey{}).(*Session) 17 - if !ok { 18 - return nil 19 - } 20 - return sess 17 + return sess, ok 21 18 } 22 19 23 - func UserFromContext(ctx context.Context) *toauth.User { 24 - sess := FromContext(ctx) 25 - if sess == nil { 20 + // UserFromContext returns optional MultiAccountUser from context. 21 + func UserFromContext(ctx context.Context) *oauth.MultiAccountUser { 22 + sess, ok := ctx.Value(ctxKey{}).(*Session) 23 + if !ok { 26 24 return nil 27 25 } 28 - return sess.User() 26 + return sess.User 29 27 }
+4 -17
appview/session/session.go
··· 1 1 package session 2 2 3 3 import ( 4 - "github.com/bluesky-social/indigo/atproto/auth/oauth" 5 - toauth "tangled.org/core/appview/oauth" 4 + "github.com/bluesky-social/indigo/atproto/client" 5 + "tangled.org/core/appview/oauth" 6 6 ) 7 7 8 - // Session is a lightweight wrapper over indigo-oauth ClientSession 9 8 type Session struct { 10 - *oauth.ClientSession 11 - } 12 - 13 - func New(atSess *oauth.ClientSession) Session { 14 - return Session{ 15 - atSess, 16 - } 17 - } 18 - 19 - func (s *Session) User() *toauth.User { 20 - return &toauth.User{ 21 - Did: string(s.Data.AccountDID), 22 - Pds: s.Data.HostURL, 23 - } 9 + User *oauth.MultiAccountUser // TODO: move MultiAccountUser def to here 10 + AtpClient *client.APIClient 24 11 }
appview/state/legacy_bridge.go

This file has not been changed.

appview/web/handler/oauth.go

This file has not been changed.

+13 -35
appview/web/handler/user_repo_issues.go
··· 53 53 54 54 // render page 55 55 err = func() error { 56 - user := session.UserFromContext(ctx) 57 - repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, repo, "", "", user) 58 - if err != nil { 59 - return err 60 - } 61 56 labelDefs, err := db.GetLabelDefinitions( 62 57 d, 63 58 orm.FilterIn("at_uri", repo.Labels), ··· 71 66 defs[l.AtUri().String()] = &l 72 67 } 73 68 return p.RepoIssues(w, pages.RepoIssuesParams{ 74 - LoggedInUser: user, 75 - RepoInfo: *repoinfo, 69 + LoggedInUser: session.UserFromContext(ctx), 70 + RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, repo, "", ""), 76 71 77 72 Issues: issues, 78 73 LabelDefs: defs, ··· 108 103 109 104 // render 110 105 err := func() error { 111 - user := session.UserFromContext(ctx) 112 - repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, issue.Repo, "", "", user) 113 - if err != nil { 114 - l.Error("failed to load repo", "err", err) 115 - return err 116 - } 117 - 118 106 reactionMap, err := db.GetReactionMap(d, 20, issue.AtUri()) 119 107 if err != nil { 120 108 l.Error("failed to get issue reactions", "err", err) ··· 122 110 } 123 111 124 112 userReactions := map[models.ReactionKind]bool{} 125 - if user != nil { 126 - userReactions = db.GetReactionStatusMap(d, user.Did, issue.AtUri()) 113 + if sess, ok := session.FromContext(ctx); ok { 114 + userReactions = db.GetReactionStatusMap(d, sess.User.Did, issue.AtUri()) 127 115 } 128 116 129 117 backlinks, err := db.GetBacklinks(d, issue.AtUri()) ··· 148 136 } 149 137 150 138 return p.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 151 - LoggedInUser: user, 152 - RepoInfo: *repoinfo, 139 + LoggedInUser: session.UserFromContext(ctx), 140 + RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, issue.Repo, "", ""), 153 141 Issue: issue, 154 142 CommentList: issue.CommentList(), 155 143 Backlinks: backlinks, 156 - OrderedReactionKinds: models.OrderedReactionKinds, 157 144 Reactions: reactionMap, 158 145 UserReacted: userReactions, 159 146 LabelDefs: defs, ··· 174 161 175 162 // render 176 163 err := func() error { 177 - user := session.UserFromContext(ctx) 178 164 repo, ok := request.RepoFromContext(ctx) 179 165 if !ok { 180 166 return fmt.Errorf("malformed request") ··· 183 169 if !ok { 184 170 return fmt.Errorf("malformed request") 185 171 } 186 - repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, repo, "", "", user) 187 - if err != nil { 188 - return err 189 - } 190 172 return p.RepoNewIssue(w, pages.RepoNewIssueParams{ 191 - LoggedInUser: user, 192 - RepoInfo: *repoinfo, 173 + LoggedInUser: session.UserFromContext(ctx), 174 + RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, repo, "", ""), 193 175 }) 194 176 }() 195 177 if err != nil { ··· 217 199 body = r.FormValue("body") 218 200 ) 219 201 220 - _, err := is.NewIssue(ctx, repo, title, body) 202 + issue, err := is.NewIssue(ctx, repo, title, body) 221 203 if err != nil { 222 204 if errors.Is(err, isvc.ErrDatabaseFail) { 223 205 p.Notice(w, noticeId, "Failed to create issue.") ··· 228 210 } 229 211 return 230 212 } 231 - p.HxLocation(w, "/") 213 + ownerSlashRepo := reporesolver.GetBaseRepoPath(r, issue.Repo) 214 + p.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId)) 232 215 } 233 216 } 234 217 ··· 251 234 252 235 // render 253 236 err := func() error { 254 - user := session.UserFromContext(ctx) 255 - repoinfo, err := rs.GetRepoInfo(ctx, repoOwnerId, issue.Repo, "", "", user) 256 - if err != nil { 257 - return err 258 - } 259 237 return p.EditIssueFragment(w, pages.EditIssueParams{ 260 - LoggedInUser: user, 261 - RepoInfo: *repoinfo, 238 + LoggedInUser: session.UserFromContext(ctx), 239 + RepoInfo: rs.MakeRepoInfo(ctx, repoOwnerId, issue.Repo, "", ""), 262 240 263 241 Issue: issue, 264 242 })
+13 -13
appview/web/middleware/auth.go
··· 21 21 return 22 22 } 23 23 24 - sess := session.New(atSess) 25 - 24 + registry := o.GetAccounts(r) 25 + sess := session.Session{ 26 + User: &oauth.MultiAccountUser{ 27 + Did: atSess.Data.AccountDID.String(), 28 + Accounts: registry.Accounts, 29 + }, 30 + AtpClient: atSess.APIClient(), 31 + } 26 32 ctx := session.IntoContext(r.Context(), sess) 27 33 next.ServeHTTP(w, r.WithContext(ctx)) 28 34 }) ··· 44 50 45 51 loginURL := fmt.Sprintf("/login?return_url=%s", url.QueryEscape(returnURL)) 46 52 47 - redirectFunc := func(w http.ResponseWriter, r *http.Request) { 48 - http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) 49 - } 50 - if r.Header.Get("HX-Request") == "true" { 51 - redirectFunc = func(w http.ResponseWriter, _ *http.Request) { 53 + if _, ok := session.FromContext(ctx); !ok { 54 + l.Debug("no session, redirecting...") 55 + if r.Header.Get("HX-Request") == "true" { 52 56 w.Header().Set("HX-Redirect", loginURL) 53 57 w.WriteHeader(http.StatusOK) 58 + } else { 59 + http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) 54 60 } 55 - } 56 - 57 - sess := session.FromContext(ctx) 58 - if sess == nil { 59 - l.Debug("no session, redirecting...") 60 - redirectFunc(w, r) 61 61 return 62 62 } 63 63
appview/web/middleware/ensuredidorhandle.go

This file has not been changed.

appview/web/middleware/log.go

This file has not been changed.

appview/web/middleware/middleware.go

This file has not been changed.

-1
appview/web/middleware/normalize.go
··· 44 44 } 45 45 46 46 next.ServeHTTP(w, r) 47 - return 48 47 }) 49 48 } 50 49 }
appview/web/middleware/paginate.go

This file has not been changed.

appview/web/middleware/resolve.go

This file has not been changed.

appview/web/readme.md

This file has not been changed.

appview/web/request/context.go

This file has not been changed.

+1 -1
appview/web/routes.go
··· 187 187 }) 188 188 189 189 r.Mount("/pulls", s.PullsRouter(mw)) 190 - r.Mount("/pipelines", s.PipelinesRouter()) 190 + r.Mount("/pipelines", s.PipelinesRouter(mw)) 191 191 r.Mount("/labels", s.LabelsRouter()) 192 192 193 193 // These routes get proxied to the knot
cmd/appview/main.go

This file has not been changed.

History

14 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
appview/{service,web}: service layer
1/3 failed, 2/3 success
expand
merge conflicts detected
expand
  • appview/pages/templates/user/login.html:33
  • appview/state/profile.go:817
  • appview/pages/templates/user/login.html:31
  • appview/pages/templates/user/login.html:93
  • appview/repo/artifact.go:251
  • appview/state/profile.go:528
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
1/3 failed, 2/3 success
expand
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
3/3 failed
expand
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
2/3 failed, 1/3 success
expand
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
appview/{service,web}: service layer
1/3 failed, 2/3 timeout
expand
expand 0 comments
1 commit
expand
draft: appview/service: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
draft: appview/service: service layer
1/3 failed, 1/3 timeout, 1/3 success
expand
expand 0 comments
1 commit
expand
draft: appview/service: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
draft: appview/service: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
draft: appview/service: service layer
3/3 success
expand
expand 0 comments
1 commit
expand
draft: appview: service layer
3/3 failed
expand
expand 0 comments
1 commit
expand
draft: appview: service layer
3/3 failed
expand
expand 0 comments