From e4dba60fcbeb4b29e7d1f2f6d1c28240289f9bd0 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Mon, 29 Sep 2025 17:01:13 +0300 Subject: [PATCH] appview/signup: set up cf turnstile Sets up Cloudflare Turnstile for fairly non-intrusive captcha. The client token is verified with CF when the user hits 'join now' (POST /signup), so this should prevent bot signups. Signed-off-by: Anirudh Oppiliappan --- appview/config/config.go | 6 ++- appview/pages/pages.go | 8 ++- appview/pages/templates/user/login.html | 2 +- appview/pages/templates/user/signup.html | 7 ++- appview/signup/signup.go | 66 +++++++++++++++++++++++- 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/appview/config/config.go b/appview/config/config.go index aed054b9..b2d21fb0 100644 --- a/appview/config/config.go +++ b/appview/config/config.go @@ -72,8 +72,10 @@ type PdsConfig struct { } type Cloudflare struct { - ApiToken string `env:"API_TOKEN"` - ZoneId string `env:"ZONE_ID"` + ApiToken string `env:"API_TOKEN"` + ZoneId string `env:"ZONE_ID"` + TurnstileSiteKey string `env:"TURNSTILE_SITE_KEY"` + TurnstileSecretKey string `env:"TURNSTILE_SECRET_KEY"` } func (cfg RedisConfig) ToURL() string { diff --git a/appview/pages/pages.go b/appview/pages/pages.go index aea80769..6825dc9f 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -226,8 +226,12 @@ func (p *Pages) Login(w io.Writer, params LoginParams) error { return p.executePlain("user/login", w, params) } -func (p *Pages) Signup(w io.Writer) error { - return p.executePlain("user/signup", w, nil) +type SignupParams struct { + CloudflareSiteKey string +} + +func (p *Pages) Signup(w io.Writer, params SignupParams) error { + return p.executePlain("user/signup", w, params) } func (p *Pages) CompleteSignup(w io.Writer) error { diff --git a/appview/pages/templates/user/login.html b/appview/pages/templates/user/login.html index be2bbe62..ebd3bafe 100644 --- a/appview/pages/templates/user/login.html +++ b/appview/pages/templates/user/login.html @@ -36,7 +36,7 @@ placeholder="akshay.tngl.sh" /> - Use your ATProto + Use your AT Protocol handle to log in. If you're unsure, this is likely your Tangled (.tngl.sh) or Bluesky (.bsky.social) account. diff --git a/appview/pages/templates/user/signup.html b/appview/pages/templates/user/signup.html index cde92862..acd77e9a 100644 --- a/appview/pages/templates/user/signup.html +++ b/appview/pages/templates/user/signup.html @@ -10,6 +10,8 @@ sign up · tangled + +
@@ -39,12 +41,15 @@ invite code, desired username, and password in the next page to complete your registration. +
+
+

- Already have an ATProto account? Login to Tangled. + Already have an AT Protocol account? Login to Tangled.

diff --git a/appview/signup/signup.go b/appview/signup/signup.go index 7a781aa7..f7a3e78f 100644 --- a/appview/signup/signup.go +++ b/appview/signup/signup.go @@ -2,9 +2,12 @@ package signup import ( "bufio" + "encoding/json" + "errors" "fmt" "log/slog" "net/http" + "net/url" "os" "strings" @@ -116,14 +119,25 @@ func (s *Signup) Router() http.Handler { func (s *Signup) signup(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - s.pages.Signup(w) + s.pages.Signup(w, pages.SignupParams{ + CloudflareSiteKey: s.config.Cloudflare.TurnstileSiteKey, + }) case http.MethodPost: if s.cf == nil { http.Error(w, "signup is disabled", http.StatusFailedDependency) + return } emailId := r.FormValue("email") + cfToken := r.FormValue("cf-turnstile-response") noticeId := "signup-msg" + + if err := s.validateCaptcha(cfToken, r); err != nil { + s.l.Warn("turnstile validation failed", "error", err) + s.pages.Notice(w, noticeId, "Captcha validation failed.") + return + } + if !email.IsValidEmail(emailId) { s.pages.Notice(w, noticeId, "Invalid email address.") return @@ -255,3 +269,53 @@ func (s *Signup) complete(w http.ResponseWriter, r *http.Request) { return } } + +type turnstileResponse struct { + Success bool `json:"success"` + ErrorCodes []string `json:"error-codes,omitempty"` + ChallengeTs string `json:"challenge_ts,omitempty"` + Hostname string `json:"hostname,omitempty"` +} + +func (s *Signup) validateCaptcha(cfToken string, r *http.Request) error { + if cfToken == "" { + return errors.New("captcha token is empty") + } + + if s.config.Cloudflare.TurnstileSecretKey == "" { + return errors.New("turnstile secret key not configured") + } + + data := url.Values{} + data.Set("secret", s.config.Cloudflare.TurnstileSecretKey) + data.Set("response", cfToken) + + // include the client IP if we have it + if remoteIP := r.Header.Get("CF-Connecting-IP"); remoteIP != "" { + data.Set("remoteip", remoteIP) + } else if remoteIP := r.Header.Get("X-Forwarded-For"); remoteIP != "" { + if ips := strings.Split(remoteIP, ","); len(ips) > 0 { + data.Set("remoteip", strings.TrimSpace(ips[0])) + } + } else { + data.Set("remoteip", r.RemoteAddr) + } + + resp, err := http.PostForm("https://challenges.cloudflare.com/turnstile/v0/siteverify", data) + if err != nil { + return fmt.Errorf("failed to verify turnstile token: %w", err) + } + defer resp.Body.Close() + + var turnstileResp turnstileResponse + if err := json.NewDecoder(resp.Body).Decode(&turnstileResp); err != nil { + return fmt.Errorf("failed to decode turnstile response: %w", err) + } + + if !turnstileResp.Success { + s.l.Warn("turnstile validation failed", "error_codes", turnstileResp.ErrorCodes) + return errors.New("turnstile validation failed") + } + + return nil +} -- 2.43.0 From 69fe99e7714ba210f75d37def51819d2b8901e27 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 30 Sep 2025 10:27:49 +0100 Subject: [PATCH] appview/pages: center captcha in signup page Change-Id: ysrsvlwnmvkmrupoupwylqsuutwukxwn Signed-off-by: oppiliappan --- appview/pages/templates/user/signup.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appview/pages/templates/user/signup.html b/appview/pages/templates/user/signup.html index acd77e9a..1edbf20b 100644 --- a/appview/pages/templates/user/signup.html +++ b/appview/pages/templates/user/signup.html @@ -41,8 +41,8 @@ invite code, desired username, and password in the next page to complete your registration. -
-
+
+
+ +
+ Repository name + +
+
Select a knot to fork into
diff --git a/appview/repo/repo.go b/appview/repo/repo.go index e82dfc80..a47b2e42 100644 --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -2129,25 +2129,29 @@ func (rp *Repo) ForkRepo(w http.ResponseWriter, r *http.Request) { } // choose a name for a fork - forkName := f.Name + forkName := r.FormValue("repo_name") + if forkName == "" { + rp.pages.Notice(w, "repo", "Repository name cannot be empty.") + return + } + // this check is *only* to see if the forked repo name already exists // in the user's account. existingRepo, err := db.GetRepo( rp.db, db.FilterEq("did", user.Did), - db.FilterEq("name", f.Name), + db.FilterEq("name", forkName), ) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - // no existing repo with this name found, we can use the name as is - } else { + if !errors.Is(err, sql.ErrNoRows) { log.Println("error fetching existing repo from db", "err", err) rp.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.") return } } else if existingRepo != nil { - // repo with this name already exists, append random string - forkName = fmt.Sprintf("%s-%s", forkName, randomString(3)) + // repo with this name already exists + rp.pages.Notice(w, "repo", "A repository with this name already exists.") + return } l = l.With("forkName", forkName) -- 2.43.0 From c8bc5f33dfcb807f40a511888637ea7c491d67c3 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Sat, 27 Sep 2025 18:29:24 +0100 Subject: [PATCH] appview/db: refactor GetPulls instead of using a massive left join, it now uses a few FilterIns. we can also get rid of the GetPull helper, it is a specialization of GetPulls that returns a single pull request. Signed-off-by: oppiliappan --- appview/db/db.go | 140 +++++++++++++++++ appview/db/pulls.go | 347 +++++++++++++---------------------------- appview/models/pull.go | 5 +- 3 files changed, 252 insertions(+), 240 deletions(-) diff --git a/appview/db/db.go b/appview/db/db.go index a4b39560..cabfaa1b 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -954,6 +954,146 @@ func Make(dbPath string) (*DB, error) { return err }) + // add generated at_uri column to pulls table + // + // this requires a full table recreation because stored columns + // cannot be added via alter + // + // disable foreign-keys for the next migration + conn.ExecContext(ctx, "pragma foreign_keys = off;") + runMigration(conn, "add-at-uri-to-pulls", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table if not exists pulls_new ( + -- identifiers + id integer primary key autoincrement, + pull_id integer not null, + at_uri text generated always as ('at://' || owner_did || '/' || 'sh.tangled.repo.pull' || '/' || rkey) stored, + + -- at identifiers + repo_at text not null, + owner_did text not null, + rkey text not null, + + -- content + title text not null, + body text not null, + target_branch text not null, + state integer not null default 0 check (state in (0, 1, 2, 3)), -- closed, open, merged, deleted + + -- source info + source_branch text, + source_repo_at text, + + -- stacking + stack_id text, + change_id text, + parent_change_id text, + + -- meta + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + + -- constraints + unique(repo_at, pull_id), + unique(at_uri), + foreign key (repo_at) references repos(at_uri) on delete cascade + ); + `) + if err != nil { + return err + } + + // transfer data + _, err = tx.Exec(` + insert into pulls_new ( + id, pull_id, repo_at, owner_did, rkey, + title, body, target_branch, state, + source_branch, source_repo_at, + stack_id, change_id, parent_change_id, + created + ) + select + id, pull_id, repo_at, owner_did, rkey, + title, body, target_branch, state, + source_branch, source_repo_at, + stack_id, change_id, parent_change_id, + created + from pulls; + `) + if err != nil { + return err + } + + // drop old table + _, err = tx.Exec(`drop table pulls`) + if err != nil { + return err + } + + // rename new table + _, err = tx.Exec(`alter table pulls_new rename to pulls`) + return err + }) + conn.ExecContext(ctx, "pragma foreign_keys = on;") + + // remove repo_at and pull_id from pull_submissions and replace with pull_at + // + // this requires a full table recreation because stored columns + // cannot be added via alter + // + // disable foreign-keys for the next migration + conn.ExecContext(ctx, "pragma foreign_keys = off;") + runMigration(conn, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table if not exists pull_submissions_new ( + -- identifiers + id integer primary key autoincrement, + pull_at text not null, + + -- content, these are immutable, and require a resubmission to update + round_number integer not null default 0, + patch text, + source_rev text, + + -- meta + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + + -- constraints + unique(pull_at, round_number), + foreign key (pull_at) references pulls(at_uri) on delete cascade + ); + `) + if err != nil { + return err + } + + // transfer data, constructing pull_at from pulls table + _, err = tx.Exec(` + insert into pull_submissions_new (id, pull_at, round_number, patch, created) + select + ps.id, + 'at://' || p.owner_did || '/sh.tangled.repo.pull/' || p.rkey, + ps.round_number, + ps.patch, + ps.created + from pull_submissions ps + join pulls p on ps.repo_at = p.repo_at and ps.pull_id = p.pull_id; + `) + if err != nil { + return err + } + + // drop old table + _, err = tx.Exec(`drop table pull_submissions`) + if err != nil { + return err + } + + // rename new table + _, err = tx.Exec(`alter table pull_submissions_new rename to pull_submissions`) + return err + }) + conn.ExecContext(ctx, "pragma foreign_keys = on;") + return &DB{db}, nil } diff --git a/appview/db/pulls.go b/appview/db/pulls.go index aad64fdc..7bc6a2af 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -3,7 +3,8 @@ package db import ( "database/sql" "fmt" - "log" + "maps" + "slices" "sort" "strings" "time" @@ -87,9 +88,9 @@ func NewPull(tx *sql.Tx, pull *models.Pull) error { pull.ID = int(id) _, err = tx.Exec(` - insert into pull_submissions (pull_id, repo_at, round_number, patch, source_rev) - values (?, ?, ?, ?, ?) - `, pull.PullId, pull.RepoAt, 0, pull.Submissions[0].Patch, pull.Submissions[0].SourceRev) + insert into pull_submissions (pull_at, round_number, patch, source_rev) + values (?, ?, ?, ?) + `, pull.PullAt(), 0, pull.Submissions[0].Patch, pull.Submissions[0].SourceRev) return err } @@ -108,7 +109,7 @@ func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { } func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, error) { - pulls := make(map[int]*models.Pull) + pulls := make(map[syntax.ATURI]*models.Pull) var conditions []string var args []any @@ -211,110 +212,23 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, pull.ParentChangeId = parentChangeId.String } - pulls[pull.PullId] = &pull + pulls[pull.PullAt()] = &pull } - // get latest round no. for each pull - inClause := strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ") - submissionsQuery := fmt.Sprintf(` - select - id, pull_id, round_number, patch, created, source_rev - from - pull_submissions - where - repo_at in (%s) and pull_id in (%s) - `, inClause, inClause) - - args = make([]any, len(pulls)*2) - idx := 0 - for _, p := range pulls { - args[idx] = p.RepoAt - idx += 1 - } - for _, p := range pulls { - args[idx] = p.PullId - idx += 1 - } - submissionsRows, err := e.Query(submissionsQuery, args...) - if err != nil { - return nil, err - } - defer submissionsRows.Close() - - for submissionsRows.Next() { - var s models.PullSubmission - var sourceRev sql.NullString - var createdAt string - err := submissionsRows.Scan( - &s.ID, - &s.PullId, - &s.RoundNumber, - &s.Patch, - &createdAt, - &sourceRev, - ) - if err != nil { - return nil, err - } - - createdTime, err := time.Parse(time.RFC3339, createdAt) - if err != nil { - return nil, err - } - s.Created = createdTime - - if sourceRev.Valid { - s.SourceRev = sourceRev.String - } - - if p, ok := pulls[s.PullId]; ok { - p.Submissions = make([]*models.PullSubmission, s.RoundNumber+1) - p.Submissions[s.RoundNumber] = &s - } - } - if err := rows.Err(); err != nil { - return nil, err - } - - // get comment count on latest submission on each pull - inClause = strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ") - commentsQuery := fmt.Sprintf(` - select - count(id), pull_id - from - pull_comments - where - submission_id in (%s) - group by - submission_id - `, inClause) - - args = []any{} + var pullAts []syntax.ATURI for _, p := range pulls { - args = append(args, p.Submissions[p.LastRoundNumber()].ID) + pullAts = append(pullAts, p.PullAt()) } - commentsRows, err := e.Query(commentsQuery, args...) + submissionsMap, err := GetPullSubmissions(e, FilterIn("pull_at", pullAts)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get submissions: %w", err) } - defer commentsRows.Close() - for commentsRows.Next() { - var commentCount, pullId int - err := commentsRows.Scan( - &commentCount, - &pullId, - ) - if err != nil { - return nil, err - } - if p, ok := pulls[pullId]; ok { - p.Submissions[p.LastRoundNumber()].Comments = make([]models.PullComment, commentCount) + for pullAt, submissions := range submissionsMap { + if p, ok := pulls[pullAt]; ok { + p.Submissions = submissions } } - if err := rows.Err(); err != nil { - return nil, err - } orderedByPullId := []*models.Pull{} for _, p := range pulls { @@ -332,142 +246,122 @@ func GetPulls(e Execer, filters ...filter) ([]*models.Pull, error) { } func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { - query := ` - select - id, - owner_did, - pull_id, - created, - title, - state, - target_branch, - repo_at, - body, - rkey, - source_branch, - source_repo_at, - stack_id, - change_id, - parent_change_id - from - pulls - where - repo_at = ? and pull_id = ? - ` - row := e.QueryRow(query, repoAt, pullId) - - var pull models.Pull - var createdAt string - var sourceBranch, sourceRepoAt, stackId, changeId, parentChangeId sql.NullString - err := row.Scan( - &pull.ID, - &pull.OwnerDid, - &pull.PullId, - &createdAt, - &pull.Title, - &pull.State, - &pull.TargetBranch, - &pull.RepoAt, - &pull.Body, - &pull.Rkey, - &sourceBranch, - &sourceRepoAt, - &stackId, - &changeId, - &parentChangeId, - ) + pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId)) if err != nil { return nil, err } - - createdTime, err := time.Parse(time.RFC3339, createdAt) - if err != nil { - return nil, err + if pulls == nil { + return nil, sql.ErrNoRows } - pull.Created = createdTime - // populate source - if sourceBranch.Valid { - pull.PullSource = &models.PullSource{ - Branch: sourceBranch.String, - } - if sourceRepoAt.Valid { - sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String) - if err != nil { - return nil, err - } - pull.PullSource.RepoAt = &sourceRepoAtParsed - } - } + return pulls[0], nil +} - if stackId.Valid { - pull.StackId = stackId.String - } - if changeId.Valid { - pull.ChangeId = changeId.String +// mapping from pull -> pull submissions +func GetPullSubmissions(e Execer, filters ...filter) (map[syntax.ATURI][]*models.PullSubmission, error) { + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) } - if parentChangeId.Valid { - pull.ParentChangeId = parentChangeId.String + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") } - submissionsQuery := ` + query := fmt.Sprintf(` select - id, pull_id, repo_at, round_number, patch, created, source_rev + id, + pull_at, + round_number, + patch, + created, + source_rev from pull_submissions - where - repo_at = ? and pull_id = ? - ` - submissionsRows, err := e.Query(submissionsQuery, repoAt, pullId) + %s + order by + round_number asc + `, whereClause) + + rows, err := e.Query(query, args...) if err != nil { return nil, err } - defer submissionsRows.Close() + defer rows.Close() - submissionsMap := make(map[int]*models.PullSubmission) + submissionMap := make(map[int]*models.PullSubmission) - for submissionsRows.Next() { + for rows.Next() { var submission models.PullSubmission - var submissionCreatedStr string - var submissionSourceRev sql.NullString - err := submissionsRows.Scan( + var createdAt string + var sourceRev sql.NullString + err := rows.Scan( &submission.ID, - &submission.PullId, - &submission.RepoAt, + &submission.PullAt, &submission.RoundNumber, &submission.Patch, - &submissionCreatedStr, - &submissionSourceRev, + &createdAt, + &sourceRev, ) if err != nil { return nil, err } - submissionCreatedTime, err := time.Parse(time.RFC3339, submissionCreatedStr) + createdTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { return nil, err } - submission.Created = submissionCreatedTime + submission.Created = createdTime - if submissionSourceRev.Valid { - submission.SourceRev = submissionSourceRev.String + if sourceRev.Valid { + submission.SourceRev = sourceRev.String } - submissionsMap[submission.ID] = &submission + submissionMap[submission.ID] = &submission + } + + if err := rows.Err(); err != nil { + return nil, err } - if err = submissionsRows.Close(); err != nil { + + // Get comments for all submissions using GetPullComments + submissionIds := slices.Collect(maps.Keys(submissionMap)) + comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds)) + if err != nil { return nil, err } - if len(submissionsMap) == 0 { - return &pull, nil + for _, comment := range comments { + if submission, ok := submissionMap[comment.SubmissionId]; ok { + submission.Comments = append(submission.Comments, comment) + } + } + + // order the submissions by pull_at + m := make(map[syntax.ATURI][]*models.PullSubmission) + for _, s := range submissionMap { + m[s.PullAt] = append(m[s.PullAt], s) } + return m, nil +} + +func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) { + var conditions []string var args []any - for k := range submissionsMap { - args = append(args, k) + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) } - inClause := strings.TrimSuffix(strings.Repeat("?, ", len(submissionsMap)), ", ") - commentsQuery := fmt.Sprintf(` + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } + + query := fmt.Sprintf(` select id, pull_id, @@ -479,21 +373,22 @@ func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { created from pull_comments - where - submission_id IN (%s) + %s order by created asc - `, inClause) - commentsRows, err := e.Query(commentsQuery, args...) + `, whereClause) + + rows, err := e.Query(query, args...) if err != nil { return nil, err } - defer commentsRows.Close() + defer rows.Close() - for commentsRows.Next() { + var comments []models.PullComment + for rows.Next() { var comment models.PullComment - var commentCreatedStr string - err := commentsRows.Scan( + var createdAt string + err := rows.Scan( &comment.ID, &comment.PullId, &comment.SubmissionId, @@ -501,46 +396,24 @@ func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { &comment.OwnerDid, &comment.CommentAt, &comment.Body, - &commentCreatedStr, + &createdAt, ) if err != nil { return nil, err } - commentCreatedTime, err := time.Parse(time.RFC3339, commentCreatedStr) - if err != nil { - return nil, err + if t, err := time.Parse(time.RFC3339, createdAt); err == nil { + comment.Created = t } - comment.Created = commentCreatedTime - - // Add the comment to its submission - if submission, ok := submissionsMap[comment.SubmissionId]; ok { - submission.Comments = append(submission.Comments, comment) - } - - } - if err = commentsRows.Err(); err != nil { - return nil, err - } - var pullSourceRepo *models.Repo - if pull.PullSource != nil { - if pull.PullSource.RepoAt != nil { - pullSourceRepo, err = GetRepoByAtUri(e, pull.PullSource.RepoAt.String()) - if err != nil { - log.Printf("failed to get repo by at uri: %v", err) - } else { - pull.PullSource.Repo = pullSourceRepo - } - } + comments = append(comments, comment) } - pull.Submissions = make([]*models.PullSubmission, len(submissionsMap)) - for _, submission := range submissionsMap { - pull.Submissions[submission.RoundNumber] = submission + if err := rows.Err(); err != nil { + return nil, err } - return &pull, nil + return comments, nil } // timeframe here is directly passed into the sql query filter, and any @@ -677,9 +550,9 @@ func DeletePull(e Execer, repoAt syntax.ATURI, pullId int) error { func ResubmitPull(e Execer, pull *models.Pull, newPatch, sourceRev string) error { newRoundNumber := len(pull.Submissions) _, err := e.Exec(` - insert into pull_submissions (pull_id, repo_at, round_number, patch, source_rev) - values (?, ?, ?, ?, ?) - `, pull.PullId, pull.RepoAt, newRoundNumber, newPatch, sourceRev) + insert into pull_submissions (pull_at, round_number, patch, source_rev) + values (?, ?, ?, ?) + `, pull.PullAt(), newRoundNumber, newPatch, sourceRev) return err } diff --git a/appview/models/pull.go b/appview/models/pull.go index 2826b00b..e17f96ae 100644 --- a/appview/models/pull.go +++ b/appview/models/pull.go @@ -125,11 +125,10 @@ func (p PullSource) AsRecord() tangled.RepoPull_Source { type PullSubmission struct { // ids - ID int - PullId int + ID int // at ids - RepoAt syntax.ATURI + PullAt syntax.ATURI // content RoundNumber int -- 2.43.0 From 8e4a85c0301c01cdbdb8565099bc312985ff1864 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Mon, 29 Sep 2025 12:21:20 +0100 Subject: [PATCH] appview/pages: add labels to pulls Signed-off-by: oppiliappan --- appview/db/pulls.go | 20 ++++++++- appview/issues/issues.go | 6 ++- appview/models/pull.go | 45 ++++++++++++++++++- appview/pages/pages.go | 3 ++ .../templates/repo/fragments/labelPanel.html | 2 +- .../repo/fragments/participants.html | 26 +++++++++++ .../pages/templates/repo/issues/issue.html | 28 +----------- appview/pages/templates/repo/pulls/pull.html | 42 ++++++++++++----- appview/pages/templates/repo/pulls/pulls.html | 7 +++ appview/pulls/pulls.go | 35 +++++++++++++++ 10 files changed, 171 insertions(+), 43 deletions(-) create mode 100644 appview/pages/templates/repo/fragments/participants.html diff --git a/appview/db/pulls.go b/appview/db/pulls.go index 7bc6a2af..bb321980 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -1,6 +1,7 @@ package db import ( + "cmp" "database/sql" "fmt" "maps" @@ -229,6 +230,16 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, p.Submissions = submissions } } + // collect allLabels for each issue + allLabels, err := GetLabels(e, FilterIn("subject", pullAts)) + if err != nil { + return nil, fmt.Errorf("failed to query labels: %w", err) + } + for pullAt, labels := range allLabels { + if p, ok := pulls[pullAt]; ok { + p.Labels = labels + } + } orderedByPullId := []*models.Pull{} for _, p := range pulls { @@ -339,12 +350,19 @@ func GetPullSubmissions(e Execer, filters ...filter) (map[syntax.ATURI][]*models } } - // order the submissions by pull_at + // group the submissions by pull_at m := make(map[syntax.ATURI][]*models.PullSubmission) for _, s := range submissionMap { m[s.PullAt] = append(m[s.PullAt], s) } + // sort each one by round number + for _, s := range m { + slices.SortFunc(s, func(a, b *models.PullSubmission) int { + return cmp.Compare(a.RoundNumber, b.RoundNumber) + }) + } + return m, nil } diff --git a/appview/issues/issues.go b/appview/issues/issues.go index 88984e44..8e4ed732 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -798,7 +798,11 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { return } - labelDefs, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels)) + labelDefs, err := db.GetLabelDefinitions( + rp.db, + db.FilterIn("at_uri", f.Repo.Labels), + db.FilterContains("scope", tangled.RepoIssueNSID), + ) if err != nil { log.Println("failed to fetch labels", err) rp.pages.Error503(w) diff --git a/appview/models/pull.go b/appview/models/pull.go index e17f96ae..39a7dcbb 100644 --- a/appview/models/pull.go +++ b/appview/models/pull.go @@ -77,7 +77,8 @@ type Pull struct { PullSource *PullSource // optionally, populate this when querying for reverse mappings - Repo *Repo + Labels LabelState + Repo *Repo } func (p Pull) AsRecord() tangled.RepoPull { @@ -206,6 +207,28 @@ func (p *Pull) IsStacked() bool { return p.StackId != "" } +func (p *Pull) Participants() []string { + participantSet := make(map[string]struct{}) + participants := []string{} + + addParticipant := func(did string) { + if _, exists := participantSet[did]; !exists { + participantSet[did] = struct{}{} + participants = append(participants, did) + } + } + + addParticipant(p.OwnerDid) + + for _, s := range p.Submissions { + for _, sp := range s.Participants() { + addParticipant(sp) + } + } + + return participants +} + func (s PullSubmission) IsFormatPatch() bool { return patchutil.IsFormatPatch(s.Patch) } @@ -220,6 +243,26 @@ func (s PullSubmission) AsFormatPatch() []types.FormatPatch { return patches } +func (s *PullSubmission) Participants() []string { + participantSet := make(map[string]struct{}) + participants := []string{} + + addParticipant := func(did string) { + if _, exists := participantSet[did]; !exists { + participantSet[did] = struct{}{} + participants = append(participants, did) + } + } + + addParticipant(s.PullAt.Authority().String()) + + for _, c := range s.Comments { + addParticipant(c.OwnerDid) + } + + return participants +} + type Stack []*Pull // position of this pull in the stack diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 6825dc9f..5f04de25 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1087,6 +1087,7 @@ type RepoPullsParams struct { FilteringBy models.PullState Stacks map[string]models.Stack Pipelines map[string]models.Pipeline + LabelDefs map[string]*models.LabelDefinition } func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { @@ -1126,6 +1127,8 @@ type RepoSinglePullParams struct { OrderedReactionKinds []models.ReactionKind Reactions map[models.ReactionKind]int UserReacted map[models.ReactionKind]bool + + LabelDefs map[string]*models.LabelDefinition } func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { diff --git a/appview/pages/templates/repo/fragments/labelPanel.html b/appview/pages/templates/repo/fragments/labelPanel.html index f475bdb8..e735d186 100644 --- a/appview/pages/templates/repo/fragments/labelPanel.html +++ b/appview/pages/templates/repo/fragments/labelPanel.html @@ -1,5 +1,5 @@ {{ define "repo/fragments/labelPanel" }} -
+
{{ template "basicLabels" . }} {{ template "kvLabels" . }}
diff --git a/appview/pages/templates/repo/fragments/participants.html b/appview/pages/templates/repo/fragments/participants.html new file mode 100644 index 00000000..7539cca9 --- /dev/null +++ b/appview/pages/templates/repo/fragments/participants.html @@ -0,0 +1,26 @@ +{{ define "repo/fragments/participants" }} + {{ $all := . }} + {{ $ps := take $all 5 }} +
+
+ Participants + {{ len $all }} +
+
+ {{ $c := "z-50 z-40 z-30 z-20 z-10" }} + {{ range $i, $p := $ps }} + + {{ end }} + + {{ if gt (len $all) 5 }} + + +{{ sub (len $all) 5 }} + + {{ end }} +
+
+{{ end }} diff --git a/appview/pages/templates/repo/issues/issue.html b/appview/pages/templates/repo/issues/issue.html index 23abf66b..7fa50d46 100644 --- a/appview/pages/templates/repo/issues/issue.html +++ b/appview/pages/templates/repo/issues/issue.html @@ -22,7 +22,7 @@ "Defs" $.LabelDefs "Subject" $.Issue.AtUri "State" $.Issue.Labels) }} - {{ template "issueParticipants" . }} + {{ template "repo/fragments/participants" $.Issue.Participants }}
{{ end }} @@ -122,32 +122,6 @@
{{ end }} -{{ define "issueParticipants" }} - {{ $all := .Issue.Participants }} - {{ $ps := take $all 5 }} -
-
- Participants - {{ len $all }} -
-
- {{ $c := "z-50 z-40 z-30 z-20 z-10" }} - {{ range $i, $p := $ps }} - - {{ end }} - - {{ if gt (len $all) 5 }} - - +{{ sub (len $all) 5 }} - - {{ end }} -
-
-{{ end }} {{ define "repoAfter" }}
diff --git a/appview/pages/templates/repo/pulls/pull.html b/appview/pages/templates/repo/pulls/pull.html index 087e0834..7873be1d 100644 --- a/appview/pages/templates/repo/pulls/pull.html +++ b/appview/pages/templates/repo/pulls/pull.html @@ -9,6 +9,24 @@ {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} {{ end }} +{{ define "repoContentLayout" }} +
+
+
+ {{ block "repoContent" . }}{{ end }} +
+ {{ block "repoAfter" . }}{{ end }} +
+
+ {{ template "repo/fragments/labelPanel" + (dict "RepoInfo" $.RepoInfo + "Defs" $.LabelDefs + "Subject" $.Pull.PullAt + "State" $.Pull.Labels) }} + {{ template "repo/fragments/participants" $.Pull.Participants }} +
+
+{{ end }} {{ define "repoContent" }} {{ template "repo/pulls/fragments/pullHeader" . }} @@ -39,13 +57,13 @@ {{ with $item }}
-
+
{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}
-
+
{{ $owner := resolve $.Pull.OwnerDid }} {{ $re := "re" }} @@ -72,16 +90,16 @@ {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} - {{ if not (eq .RoundNumber 0) }} - - {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} - - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} - - + {{ if ne $idx 0 }} + + {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} + + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} + {{ end }} +
@@ -146,7 +164,7 @@
{{ range $cidx, $c := .Comments }} -
+
{{ if gt $cidx 0 }}
{{ end }} diff --git a/appview/pages/templates/repo/pulls/pulls.html b/appview/pages/templates/repo/pulls/pulls.html index bdc0b065..6541a54c 100644 --- a/appview/pages/templates/repo/pulls/pulls.html +++ b/appview/pages/templates/repo/pulls/pulls.html @@ -108,6 +108,13 @@ {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} {{ end }} + + {{ $state := .Labels }} + {{ range $k, $d := $.LabelDefs }} + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} + {{ end }} + {{ end }}
{{ if .StackId }} diff --git a/appview/pulls/pulls.go b/appview/pulls/pulls.go index 77f35577..e5900d4b 100644 --- a/appview/pulls/pulls.go +++ b/appview/pulls/pulls.go @@ -200,6 +200,22 @@ func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) { userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.PullAt()) } + labelDefs, err := db.GetLabelDefinitions( + s.db, + db.FilterIn("at_uri", f.Repo.Labels), + db.FilterContains("scope", tangled.RepoPullNSID), + ) + if err != nil { + log.Println("failed to fetch labels", err) + s.pages.Error503(w) + return + } + + defs := make(map[string]*models.LabelDefinition) + for _, l := range labelDefs { + defs[l.AtUri().String()] = &l + } + s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ LoggedInUser: user, RepoInfo: repoInfo, @@ -213,6 +229,8 @@ func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) { OrderedReactionKinds: models.OrderedReactionKinds, Reactions: reactionCountMap, UserReacted: userReactions, + + LabelDefs: defs, }) } @@ -557,10 +575,27 @@ func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) { m[p.Sha] = p } + labelDefs, err := db.GetLabelDefinitions( + s.db, + db.FilterIn("at_uri", f.Repo.Labels), + db.FilterContains("scope", tangled.RepoPullNSID), + ) + if err != nil { + log.Println("failed to fetch labels", err) + s.pages.Error503(w) + return + } + + defs := make(map[string]*models.LabelDefinition) + for _, l := range labelDefs { + defs[l.AtUri().String()] = &l + } + s.pages.RepoPulls(w, pages.RepoPullsParams{ LoggedInUser: s.oauth.GetUser(r), RepoInfo: f.RepoInfo(user), Pulls: pulls, + LabelDefs: defs, FilteringBy: state, Stacks: stacks, Pipelines: m, -- 2.43.0 From 6b0222850b1e4ea5f1f8526c0b81205e28ed15c7 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Mon, 29 Sep 2025 16:54:31 +0100 Subject: [PATCH] appview/notifications: code cleanup for notifier GetRepo is a specialization for GetRepos which is to be used when the filters are primary keys. Signed-off-by: oppiliappan --- appview/db/notifications.go | 12 ------ appview/notifications/notifications.go | 4 +- appview/notify/db/db.go | 56 ++++---------------------- 3 files changed, 11 insertions(+), 61 deletions(-) diff --git a/appview/db/notifications.go b/appview/db/notifications.go index f65dcc00..68e3b825 100644 --- a/appview/db/notifications.go +++ b/appview/db/notifications.go @@ -248,18 +248,6 @@ func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, erro return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) } -// GetNotifications retrieves notifications for a user with pagination (legacy method for backward compatibility) -func (d *DB) GetNotifications(ctx context.Context, userDID string, limit, offset int) ([]*models.Notification, error) { - page := pagination.Page{Limit: limit, Offset: offset} - return GetNotificationsPaginated(d.DB, page, FilterEq("recipient_did", userDID)) -} - -// GetNotificationsWithEntities retrieves notifications with entities for a user with pagination -func (d *DB) GetNotificationsWithEntities(ctx context.Context, userDID string, limit, offset int) ([]*models.NotificationWithEntity, error) { - page := pagination.Page{Limit: limit, Offset: offset} - return GetNotificationsWithEntities(d.DB, page, FilterEq("recipient_did", userDID)) -} - func (d *DB) GetUnreadNotificationCount(ctx context.Context, userDID string) (int, error) { recipientFilter := FilterEq("recipient_did", userDID) readFilter := FilterEq("read", 0) diff --git a/appview/notifications/notifications.go b/appview/notifications/notifications.go index 8ba1c8c7..de7c5b22 100644 --- a/appview/notifications/notifications.go +++ b/appview/notifications/notifications.go @@ -10,6 +10,7 @@ import ( "tangled.org/core/appview/middleware" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages" + "tangled.org/core/appview/pagination" ) type Notifications struct { @@ -61,7 +62,8 @@ func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request } } - notifications, err := n.db.GetNotificationsWithEntities(r.Context(), userDid, limit+1, offset) + page := pagination.Page{Limit: limit + 1, Offset: offset} + notifications, err := db.GetNotificationsWithEntities(n.db, page, db.FilterEq("recipient_did", userDid)) if err != nil { log.Println("failed to get notifications:", err) n.pages.Error500(w) diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go index ef75550e..900428ea 100644 --- a/appview/notify/db/db.go +++ b/appview/notify/db/db.go @@ -30,16 +30,11 @@ func (n *databaseNotifier) NewRepo(ctx context.Context, repo *models.Repo) { func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) { var err error - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(star.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt))) if err != nil { log.Printf("NewStar: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewStar: no repo found for %s", star.RepoAt) - return - } - repo := repos[0] // don't notify yourself if repo.Did == star.StarredByDid { @@ -76,16 +71,11 @@ func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) { } func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt))) if err != nil { log.Printf("NewIssue: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewIssue: no repo found for %s", issue.RepoAt) - return - } - repo := repos[0] if repo.Did == issue.Did { return @@ -129,16 +119,11 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. } issue := issues[0] - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt))) if err != nil { log.Printf("NewIssueComment: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewIssueComment: no repo found for %s", issue.RepoAt) - return - } - repo := repos[0] recipients := make(map[string]bool) @@ -211,16 +196,11 @@ func (n *databaseNotifier) DeleteFollow(ctx context.Context, follow *models.Foll } func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) if err != nil { log.Printf("NewPull: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewPull: no repo found for %s", pull.RepoAt) - return - } - repo := repos[0] if repo.Did == pull.OwnerDid { return @@ -266,16 +246,11 @@ func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.P } pull := pulls[0] - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", comment.RepoAt)) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt)) if err != nil { log.Printf("NewPullComment: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewPullComment: no repo found for %s", comment.RepoAt) - return - } - repo := repos[0] recipients := make(map[string]bool) @@ -335,16 +310,11 @@ func (n *databaseNotifier) NewString(ctx context.Context, string *models.String) func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) { // Get repo details - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt))) if err != nil { log.Printf("NewIssueClosed: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewIssueClosed: no repo found for %s", issue.RepoAt) - return - } - repo := repos[0] // Don't notify yourself if repo.Did == issue.Did { @@ -380,16 +350,11 @@ func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Iss func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) { // Get repo details - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) if err != nil { log.Printf("NewPullMerged: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewPullMerged: no repo found for %s", pull.RepoAt) - return - } - repo := repos[0] // Don't notify yourself if repo.Did == pull.OwnerDid { @@ -425,16 +390,11 @@ func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) { // Get repo details - repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt))) + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) if err != nil { log.Printf("NewPullClosed: failed to get repos: %v", err) return } - if len(repos) == 0 { - log.Printf("NewPullClosed: no repo found for %s", pull.RepoAt) - return - } - repo := repos[0] // Don't notify yourself if repo.Did == pull.OwnerDid { -- 2.43.0 From b0eacfbd42f4d6580cc39d2b671fa18c3b357f84 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Mon, 29 Sep 2025 16:54:31 +0100 Subject: [PATCH] appview/pages: improve notification styles Signed-off-by: oppiliappan --- appview/models/notifications.go | 30 ++++++- .../notifications/fragments/item.html | 78 +++++++++++++++---- .../pages/templates/notifications/list.html | 34 +++----- 3 files changed, 103 insertions(+), 39 deletions(-) diff --git a/appview/models/notifications.go b/appview/models/notifications.go index bb467fc2..441f3df5 100644 --- a/appview/models/notifications.go +++ b/appview/models/notifications.go @@ -1,6 +1,8 @@ package models -import "time" +import ( + "time" +) type NotificationType string @@ -32,6 +34,32 @@ type Notification struct { PullId *int64 } +// lucide icon that represents this notification +func (n *Notification) Icon() string { + switch n.Type { + case NotificationTypeRepoStarred: + return "star" + case NotificationTypeIssueCreated: + return "circle-dot" + case NotificationTypeIssueCommented: + return "message-square" + case NotificationTypeIssueClosed: + return "ban" + case NotificationTypePullCreated: + return "git-pull-request-create" + case NotificationTypePullCommented: + return "message-square" + case NotificationTypePullMerged: + return "git-merge" + case NotificationTypePullClosed: + return "git-pull-request-closed" + case NotificationTypeFollowed: + return "user-plus" + default: + return "" + } +} + type NotificationWithEntity struct { *Notification Repo *Repo diff --git a/appview/pages/templates/notifications/fragments/item.html b/appview/pages/templates/notifications/fragments/item.html index 7d663703..708e2b3d 100644 --- a/appview/pages/templates/notifications/fragments/item.html +++ b/appview/pages/templates/notifications/fragments/item.html @@ -1,19 +1,69 @@ {{define "notifications/fragments/item"}} -
- {{if .Issue}} - {{template "issueNotification" .}} - {{else if .Pull}} - {{template "pullNotification" .}} - {{else if .Repo}} - {{template "repoNotification" .}} - {{else if eq .Type "followed"}} - {{template "followNotification" .}} - {{else}} - {{template "genericNotification" .}} - {{end}} -
+
+ + {{ template "notificationIcon" . }} +
+ {{ template "notificationHeader" . }} + {{ template "notificationSummary" . }} +
+ +
{{end}} +{{ define "notificationIcon" }} +
+ +
+ {{ i .Icon "size-3 text-black dark:text-white" }} +
+
+{{ end }} + +{{ define "notificationHeader" }} + {{ $actor := resolve .ActorDid }} + + {{ $actor }} + {{ if eq .Type "repo_starred" }} + starred {{ resolve .Repo.Did }}/{{ .Repo.Name }} + {{ else if eq .Type "issue_created" }} + opened an issue + {{ else if eq .Type "issue_commented" }} + commented on an issue + {{ else if eq .Type "issue_closed" }} + closed an issue + {{ else if eq .Type "pull_created" }} + created a pull request + {{ else if eq .Type "pull_commented" }} + commented on a pull request + {{ else if eq .Type "pull_merged" }} + merged a pull request + {{ else if eq .Type "pull_closed" }} + closed a pull request + {{ else if eq .Type "followed" }} + followed you + {{ else }} + {{ end }} +{{ end }} + +{{ define "notificationSummary" }} + {{ if eq .Type "repo_starred" }} + + {{ else if .Issue }} + #{{.Issue.IssueId}} {{.Issue.Title}} on {{resolve .Repo.Did}}/{{.Repo.Name}} + {{ else if .Pull }} + #{{.Pull.PullId}} {{.Pull.Title}} on {{resolve .Repo.Did}}/{{.Repo.Name}} + {{ else if eq .Type "followed" }} + + {{ else }} + {{ end }} +{{ end }} + {{define "issueNotification"}} {{$url := printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Name .Issue.IssueId}} {{end}} - {{template "user/fragments/picHandle" (resolve .ActorDid)}} + {{template "user/fragments/picHandle" .ActorDid}} {{if eq .Type "issue_created"}} opened issue {{else if eq .Type "issue_commented"}} diff --git a/appview/pages/templates/notifications/list.html b/appview/pages/templates/notifications/list.html index 7362e6db..e44177ea 100644 --- a/appview/pages/templates/notifications/list.html +++ b/appview/pages/templates/notifications/list.html @@ -1,8 +1,8 @@ {{ define "title" }}notifications{{ end }} {{ define "content" }} -
-
+ -
{{if .Notifications}} -
+
{{range .Notifications}} {{template "notifications/fragments/item" .}} {{end}}
- {{if .HasMore}} -
- -
- {{end}} {{else}} -
-
- {{ i "bell-off" "w-16 h-16" }} +
+
+
+ {{ i "bell-off" "w-16 h-16" }} +
+

No notifications

+

When you receive notifications, they'll appear here.

-

No notifications

-

When you receive notifications, they'll appear here.

{{end}} -
{{ end }} -- 2.43.0 From 054a6a04b2df2fe448756c03f27c1485cc91ec01 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 30 Sep 2025 15:11:31 +0100 Subject: [PATCH] appview/notifications: fix pagination Signed-off-by: oppiliappan --- appview/db/notifications.go | 31 +++++---- appview/notifications/notifications.go | 65 +++++++++---------- appview/pages/pages.go | 7 +- .../pages/templates/notifications/list.html | 63 +++++++++++++----- appview/pagination/page.go | 2 +- 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/appview/db/notifications.go b/appview/db/notifications.go index 68e3b825..6282ef56 100644 --- a/appview/db/notifications.go +++ b/appview/db/notifications.go @@ -3,7 +3,9 @@ package db import ( "context" "database/sql" + "errors" "fmt" + "strings" "time" "tangled.org/core/appview/models" @@ -248,22 +250,25 @@ func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, erro return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) } -func (d *DB) GetUnreadNotificationCount(ctx context.Context, userDID string) (int, error) { - recipientFilter := FilterEq("recipient_did", userDID) - readFilter := FilterEq("read", 0) +func CountNotifications(e Execer, filters ...filter) (int64, error) { + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) + } - query := fmt.Sprintf(` - SELECT COUNT(*) - FROM notifications - WHERE %s AND %s - `, recipientFilter.Condition(), readFilter.Condition()) + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } - args := append(recipientFilter.Arg(), readFilter.Arg()...) + query := fmt.Sprintf(`select count(1) from notifications %s`, whereClause) + var count int64 + err := e.QueryRow(query, args...).Scan(&count) - var count int - err := d.DB.QueryRowContext(ctx, query, args...).Scan(&count) - if err != nil { - return 0, fmt.Errorf("failed to get unread count: %w", err) + if !errors.Is(err, sql.ErrNoRows) && err != nil { + return 0, err } return count, nil diff --git a/appview/notifications/notifications.go b/appview/notifications/notifications.go index de7c5b22..bb017df4 100644 --- a/appview/notifications/notifications.go +++ b/appview/notifications/notifications.go @@ -1,6 +1,7 @@ package notifications import ( + "fmt" "log" "net/http" "strconv" @@ -32,7 +33,7 @@ func (n *Notifications) Router(mw *middleware.Middleware) http.Handler { r.Use(middleware.AuthMiddleware(n.oauth)) - r.Get("/", n.notificationsPage) + r.With(middleware.Paginate).Get("/", n.notificationsPage) r.Get("/count", n.getUnreadCount) r.Post("/{id}/read", n.markRead) @@ -45,36 +46,33 @@ func (n *Notifications) Router(mw *middleware.Middleware) http.Handler { func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) { userDid := n.oauth.GetDid(r) - limitStr := r.URL.Query().Get("limit") - offsetStr := r.URL.Query().Get("offset") - - limit := 20 // default - if limitStr != "" { - if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 { - limit = l - } + page, ok := r.Context().Value("page").(pagination.Page) + if !ok { + log.Println("failed to get page") + page = pagination.FirstPage() } - offset := 0 // default - if offsetStr != "" { - if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 { - offset = o - } + total, err := db.CountNotifications( + n.db, + db.FilterEq("recipient_did", userDid), + ) + if err != nil { + log.Println("failed to get total notifications:", err) + n.pages.Error500(w) + return } - page := pagination.Page{Limit: limit + 1, Offset: offset} - notifications, err := db.GetNotificationsWithEntities(n.db, page, db.FilterEq("recipient_did", userDid)) + notifications, err := db.GetNotificationsWithEntities( + n.db, + page, + db.FilterEq("recipient_did", userDid), + ) if err != nil { log.Println("failed to get notifications:", err) n.pages.Error500(w) return } - hasMore := len(notifications) > limit - if hasMore { - notifications = notifications[:limit] - } - err = n.db.MarkAllNotificationsRead(r.Context(), userDid) if err != nil { log.Println("failed to mark notifications as read:", err) @@ -88,27 +86,22 @@ func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request return } - params := pages.NotificationsParams{ + fmt.Println(n.pages.Notifications(w, pages.NotificationsParams{ LoggedInUser: user, Notifications: notifications, UnreadCount: unreadCount, - HasMore: hasMore, - NextOffset: offset + limit, - Limit: limit, - } - - err = n.pages.Notifications(w, params) - if err != nil { - log.Println("failed to load notifs:", err) - n.pages.Error500(w) - return - } + Page: page, + Total: total, + })) } func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) { - userDid := n.oauth.GetDid(r) - - count, err := n.db.GetUnreadNotificationCount(r.Context(), userDid) + user := n.oauth.GetUser(r) + count, err := db.CountNotifications( + n.db, + db.FilterEq("recipient_did", user.Did), + db.FilterEq("read", 0), + ) if err != nil { http.Error(w, "Failed to get unread count", http.StatusInternalServerError) return diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 5f04de25..95c6fb64 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -326,9 +326,8 @@ type NotificationsParams struct { LoggedInUser *oauth.User Notifications []*models.NotificationWithEntity UnreadCount int - HasMore bool - NextOffset int - Limit int + Page pagination.Page + Total int64 } func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { @@ -344,7 +343,7 @@ func (p *Pages) NotificationItem(w io.Writer, params NotificationItemParams) err } type NotificationCountParams struct { - Count int + Count int64 } func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { diff --git a/appview/pages/templates/notifications/list.html b/appview/pages/templates/notifications/list.html index e44177ea..4e0ffd73 100644 --- a/appview/pages/templates/notifications/list.html +++ b/appview/pages/templates/notifications/list.html @@ -11,22 +11,55 @@
- {{if .Notifications}} -
- {{range .Notifications}} - {{template "notifications/fragments/item" .}} - {{end}} -
+ {{if .Notifications}} +
+ {{range .Notifications}} + {{template "notifications/fragments/item" .}} + {{end}} +
- {{else}} -
-
-
- {{ i "bell-off" "w-16 h-16" }} -
-

No notifications

-

When you receive notifications, they'll appear here.

+ {{else}} +
+
+
+ {{ i "bell-off" "w-16 h-16" }}
+

No notifications

+

When you receive notifications, they'll appear here.

- {{end}} +
+ {{end}} + + {{ template "pagination" . }} +{{ end }} + +{{ define "pagination" }} +
+ {{ if gt .Page.Offset 0 }} + {{ $prev := .Page.Previous }} + + {{ i "chevron-left" "w-4 h-4" }} + previous + + {{ else }} +
+ {{ end }} + + {{ $next := .Page.Next }} + {{ if lt $next.Offset .Total }} + {{ $next := .Page.Next }} + + next + {{ i "chevron-right" "w-4 h-4" }} + + {{ end }} +
{{ end }} diff --git a/appview/pagination/page.go b/appview/pagination/page.go index e9f47fa1..b3dc781b 100644 --- a/appview/pagination/page.go +++ b/appview/pagination/page.go @@ -8,7 +8,7 @@ type Page struct { func FirstPage() Page { return Page{ Offset: 0, - Limit: 10, + Limit: 30, } } -- 2.43.0 From 5c9bf597da9a0f19635187a10f6711dd1917a9d7 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 30 Sep 2025 16:45:25 +0100 Subject: [PATCH] appview/notifications: add link element to each notification Change-Id: xspttmxkkqvtpxmprpsupqvxyknumlyn --- .../notifications/fragments/item.html | 235 ++---------------- 1 file changed, 27 insertions(+), 208 deletions(-) diff --git a/appview/pages/templates/notifications/fragments/item.html b/appview/pages/templates/notifications/fragments/item.html index 708e2b3d..1ce43fb9 100644 --- a/appview/pages/templates/notifications/fragments/item.html +++ b/appview/pages/templates/notifications/fragments/item.html @@ -1,19 +1,19 @@ {{define "notifications/fragments/item"}} -
+ +
+ {{ template "notificationIcon" . }} +
+ {{ template "notificationHeader" . }} + {{ template "notificationSummary" . }} +
- {{ template "notificationIcon" . }} -
- {{ template "notificationHeader" . }} - {{ template "notificationSummary" . }}
- -
+
{{end}} {{ define "notificationIcon" }} @@ -64,199 +64,18 @@ {{ end }} {{ end }} -{{define "issueNotification"}} -{{$url := printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Name .Issue.IssueId}} - -
-
- -
- {{if eq .Type "issue_created"}} - - {{ i "circle-dot" "w-4 h-4" }} - - {{else if eq .Type "issue_commented"}} - - {{ i "message-circle" "w-4 h-4" }} - - {{else if eq .Type "issue_closed"}} - - {{ i "ban" "w-4 h-4" }} - - {{end}} - {{template "user/fragments/picHandle" .ActorDid}} - {{if eq .Type "issue_created"}} - opened issue - {{else if eq .Type "issue_commented"}} - commented on issue - {{else if eq .Type "issue_closed"}} - closed issue - {{end}} - {{if not .Read}} -
- {{end}} -
- -
- #{{.Issue.IssueId}} - {{.Issue.Title}} - on - {{resolve .Repo.Did}}/{{.Repo.Name}} -
-
- -
- {{ template "repo/fragments/time" .Created }} -
-
-
-{{end}} - -{{define "pullNotification"}} -{{$url := printf "/%s/%s/pulls/%d" (resolve .Repo.Did) .Repo.Name .Pull.PullId}} - -
-
-
- {{if eq .Type "pull_created"}} - - {{ i "git-pull-request-create" "w-4 h-4" }} - - {{else if eq .Type "pull_commented"}} - - {{ i "message-circle" "w-4 h-4" }} - - {{else if eq .Type "pull_merged"}} - - {{ i "git-merge" "w-4 h-4" }} - - {{else if eq .Type "pull_closed"}} - - {{ i "git-pull-request-closed" "w-4 h-4" }} - - {{end}} - {{template "user/fragments/picHandle" (resolve .ActorDid)}} - {{if eq .Type "pull_created"}} - opened pull request - {{else if eq .Type "pull_commented"}} - commented on pull request - {{else if eq .Type "pull_merged"}} - merged pull request - {{else if eq .Type "pull_closed"}} - closed pull request - {{end}} - {{if not .Read}} -
- {{end}} -
- -
- #{{.Pull.PullId}} - {{.Pull.Title}} - on - {{resolve .Repo.Did}}/{{.Repo.Name}} -
-
- -
- {{ template "repo/fragments/time" .Created }} -
-
-
-{{end}} - -{{define "repoNotification"}} -{{$url := printf "/%s/%s" (resolve .Repo.Did) .Repo.Name}} - -
-
- - {{ i "star" "w-4 h-4" }} - - -
- -
- {{template "user/fragments/picHandle" (resolve .ActorDid)}} - starred - {{resolve .Repo.Did}}/{{.Repo.Name}} - {{if not .Read}} -
- {{end}} -
-
-
- -
- {{ template "repo/fragments/time" .Created }} -
-
-
-{{end}} - -{{define "followNotification"}} -{{$url := printf "/%s" (resolve .ActorDid)}} - -
-
- - {{ i "user-plus" "w-4 h-4" }} - - -
-
- {{template "user/fragments/picHandle" (resolve .ActorDid)}} - followed you - {{if not .Read}} -
- {{end}} -
-
-
- -
- {{ template "repo/fragments/time" .Created }} -
-
-
-{{end}} - -{{define "genericNotification"}} - -
-
- - {{ i "bell" "w-4 h-4" }} - - -
-
- New notification - {{if not .Read}} -
- {{end}} -
-
-
+{{ define "notificationUrl" }} + {{ $url := "" }} + {{ if eq .Type "repo_starred" }} + {{$url = printf "/%s/%s" (resolve .Repo.Did) .Repo.Name}} + {{ else if .Issue }} + {{$url = printf "/%s/%s/issues/%d" (resolve .Repo.Did) .Repo.Name .Issue.IssueId}} + {{ else if .Pull }} + {{$url = printf "/%s/%s/pulls/%d" (resolve .Repo.Did) .Repo.Name .Pull.PullId}} + {{ else if eq .Type "followed" }} + {{$url = printf "/%s" (resolve .ActorDid)}} + {{ else }} + {{ end }} -
- {{ template "repo/fragments/time" .Created }} -
-
-
-{{end}} + {{ $url }} +{{ end }} -- 2.43.0 From c174b225e663de2b9878efabb479217ac3a339dc Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 30 Sep 2025 23:20:44 +0100 Subject: [PATCH] appview/db: populate pull-source in GetPulls Change-Id: sqtwqkynvunwmxzuknntsqukozxvrmyz Signed-off-by: oppiliappan --- appview/db/pulls.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/appview/db/pulls.go b/appview/db/pulls.go index bb321980..409c615d 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -3,6 +3,7 @@ package db import ( "cmp" "database/sql" + "errors" "fmt" "maps" "slices" @@ -230,6 +231,7 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, p.Submissions = submissions } } + // collect allLabels for each issue allLabels, err := GetLabels(e, FilterIn("subject", pullAts)) if err != nil { @@ -241,6 +243,29 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, } } + // collect pull source for all pulls that need it + var sourceAts []syntax.ATURI + for _, p := range pulls { + if p.PullSource.RepoAt != nil { + sourceAts = append(sourceAts, *p.PullSource.RepoAt) + } + } + sourceRepos, err := GetRepos(e, 0, FilterIn("at_uri", sourceAts)) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("failed to get source repos: %w", err) + } + sourceRepoMap := make(map[syntax.ATURI]*models.Repo) + for _, r := range sourceRepos { + sourceRepoMap[r.RepoAt()] = &r + } + for _, p := range pulls { + if p.PullSource.RepoAt != nil { + if sourceRepo, ok := sourceRepoMap[*p.PullSource.RepoAt]; ok { + p.PullSource.Repo = sourceRepo + } + } + } + orderedByPullId := []*models.Pull{} for _, p := range pulls { orderedByPullId = append(orderedByPullId, p) -- 2.43.0 From 218b13d4e7e7cb4e96a0e8f20f71bb0b93afabf4 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 1 Oct 2025 00:09:46 +0100 Subject: [PATCH] knotserver: bump version to 1.9.1-alpha Change-Id: yxyymqlurmuwytvmmvuwnzmrmnvnvktw Signed-off-by: oppiliappan --- nix/pkgs/knot-unwrapped.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/pkgs/knot-unwrapped.nix b/nix/pkgs/knot-unwrapped.nix index 0656a0f5..accf029d 100644 --- a/nix/pkgs/knot-unwrapped.nix +++ b/nix/pkgs/knot-unwrapped.nix @@ -4,7 +4,7 @@ sqlite-lib, src, }: let - version = "1.9.0-alpha"; + version = "1.9.1-alpha"; in buildGoApplication { pname = "knot"; -- 2.43.0 From 5df3d5df76ee20ad0ad818c3aa76d92788672873 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Wed, 1 Oct 2025 11:34:36 +0300 Subject: [PATCH] appview/pages: fix brand link Change-Id: zyzkwwzvstyrvnvkkoztvpqvmuzuvpwn Signed-off-by: Anirudh Oppiliappan --- appview/pages/templates/layouts/fragments/footer.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appview/pages/templates/layouts/fragments/footer.html b/appview/pages/templates/layouts/fragments/footer.html index 11fe81f7..dd7ab5eb 100644 --- a/appview/pages/templates/layouts/fragments/footer.html +++ b/appview/pages/templates/layouts/fragments/footer.html @@ -23,7 +23,7 @@ {{ i "book-open" $iconStyle }} blog {{ i "book" $iconStyle }} docs {{ i "code" $iconStyle }} source - {{ i "paintbrush" $iconStyle }} brand + {{ i "paintbrush" $iconStyle }} brand
-- 2.43.0 From b3c2b81d8537135a44ca75274fbc23e6d0c9068c Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 1 Oct 2025 11:56:51 +0100 Subject: [PATCH] appview/db: handle nils Change-Id: pppzzywwpwzznpttulyukrzunpsnxyux Signed-off-by: oppiliappan --- appview/db/pulls.go | 4 ++-- appview/signup/signup.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appview/db/pulls.go b/appview/db/pulls.go index 409c615d..cacb6c42 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -246,7 +246,7 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, // collect pull source for all pulls that need it var sourceAts []syntax.ATURI for _, p := range pulls { - if p.PullSource.RepoAt != nil { + if p.PullSource != nil && p.PullSource.RepoAt != nil { sourceAts = append(sourceAts, *p.PullSource.RepoAt) } } @@ -259,7 +259,7 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, sourceRepoMap[r.RepoAt()] = &r } for _, p := range pulls { - if p.PullSource.RepoAt != nil { + if p.PullSource != nil && p.PullSource.RepoAt != nil { if sourceRepo, ok := sourceRepoMap[*p.PullSource.RepoAt]; ok { p.PullSource.Repo = sourceRepo } diff --git a/appview/signup/signup.go b/appview/signup/signup.go index f7a3e78f..c96b6b9a 100644 --- a/appview/signup/signup.go +++ b/appview/signup/signup.go @@ -133,7 +133,7 @@ func (s *Signup) signup(w http.ResponseWriter, r *http.Request) { noticeId := "signup-msg" if err := s.validateCaptcha(cfToken, r); err != nil { - s.l.Warn("turnstile validation failed", "error", err) + s.l.Warn("turnstile validation failed", "error", err, "email", emailId) s.pages.Notice(w, noticeId, "Captcha validation failed.") return } -- 2.43.0 From 3ff045c06e6bb2df8ee088c812c46d8af83ac174 Mon Sep 17 00:00:00 2001 From: Vidya Sagar VOBBILISETTI Date: Wed, 1 Oct 2025 14:20:09 +0200 Subject: [PATCH] appview/pages: Update .sh to .org in ssh clone dropdown Signed-off-by: Vidya Sagar VOBBILISETTI --- appview/pages/templates/repo/fragments/cloneDropdown.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appview/pages/templates/repo/fragments/cloneDropdown.html b/appview/pages/templates/repo/fragments/cloneDropdown.html index e093fd56..708f12af 100644 --- a/appview/pages/templates/repo/fragments/cloneDropdown.html +++ b/appview/pages/templates/repo/fragments/cloneDropdown.html @@ -1,7 +1,7 @@ {{ define "repo/fragments/cloneDropdown" }} {{ $knot := .RepoInfo.Knot }} {{ if eq $knot "knot1.tangled.sh" }} - {{ $knot = "tangled.sh" }} + {{ $knot = "tangled.org" }} {{ end }}
-- 2.43.0 From 6dfc5e51b975e6560cd7d5a5a1608e084940aabb Mon Sep 17 00:00:00 2001 From: Tangled Date: Wed, 1 Oct 2025 13:53:21 +0000 Subject: [PATCH] docs/spindle: fix grammar Change-Id: lvptqzqspskmmkxrurzzptotrwpsqtsr Signed-off-by: Tangled --- docs/spindle/pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spindle/pipeline.md b/docs/spindle/pipeline.md index d57f253c..ee87062d 100644 --- a/docs/spindle/pipeline.md +++ b/docs/spindle/pipeline.md @@ -21,7 +21,7 @@ The first thing to add to a workflow is the trigger, which defines when a workfl - `manual`: The workflow can be triggered manually. - `branch`: This is a **required** field that defines which branches the workflow should run for. If used with the `push` event, commits to the branch(es) listed here will trigger the workflow. If used with the `pull_request` event, updates to pull requests targeting the branch(es) listed here will trigger the workflow. This field has no effect with the `manual` event. -For example, if you'd like define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with: +For example, if you'd like to define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with: ```yaml when: -- 2.43.0 From 32b7c17e266e0da0f0e1e6e6a3821cd8dd3b8362 Mon Sep 17 00:00:00 2001 From: Samuel Shuert Date: Wed, 1 Oct 2025 16:51:31 +0000 Subject: [PATCH] knotserver/config: fix default appview endpoint tangled.sh has been moved to tangled.org Signed-off-by: Samuel Shuert --- knotserver/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knotserver/config/config.go b/knotserver/config/config.go index f8358d3f..edaa95cb 100644 --- a/knotserver/config/config.go +++ b/knotserver/config/config.go @@ -41,7 +41,7 @@ type Config struct { Repo Repo `env:",prefix=KNOT_REPO_"` Server Server `env:",prefix=KNOT_SERVER_"` Git Git `env:",prefix=KNOT_GIT_"` - AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.sh"` + AppViewEndpoint string `env:"APPVIEW_ENDPOINT, default=https://tangled.org"` } func Load(ctx context.Context) (*Config, error) { -- 2.43.0 From 6fc666872ce489581611f9bc214270b0296a039f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20R=C3=B8mer=20Hesselbjerg?= Date: Tue, 30 Sep 2025 21:20:55 +0200 Subject: [PATCH] Remove redundant grid item properties in layouts/base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jens Rømer Hesselbjerg --- appview/pages/templates/layouts/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appview/pages/templates/layouts/base.html b/appview/pages/templates/layouts/base.html index 4fc18a81..5f54b4d1 100644 --- a/appview/pages/templates/layouts/base.html +++ b/appview/pages/templates/layouts/base.html @@ -40,13 +40,13 @@ {{ block "mainLayout" . }}
{{ block "contentLayout" . }} -
+
{{ block "content" . }}{{ end }}
{{ end }} {{ block "contentAfterLayout" . }} -
+
{{ block "contentAfter" . }}{{ end }}
{{ end }} -- 2.43.0 From 9e9e69efefca65d49f60a9db308a2e34a0963324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20R=C3=B8mer=20Hesselbjerg?= Date: Thu, 2 Oct 2025 08:35:52 +0200 Subject: [PATCH] Header and footer background is full page width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jens Rømer Hesselbjerg --- appview/pages/templates/layouts/base.html | 21 ++++++++++--------- .../templates/layouts/fragments/footer.html | 4 ++-- .../templates/layouts/fragments/topbar.html | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/appview/pages/templates/layouts/base.html b/appview/pages/templates/layouts/base.html index 5f54b4d1..b88e74e4 100644 --- a/appview/pages/templates/layouts/base.html +++ b/appview/pages/templates/layouts/base.html @@ -21,10 +21,9 @@ {{ block "title" . }}{{ end }} · tangled {{ block "extrameta" . }}{{ end }} - + {{ block "topbarLayout" . }} -
+
{{ if .LoggedInUser }}
- {{ block "contentLayout" . }} +
+
+ {{ block "contentLayout" . }}
{{ block "content" . }}{{ end }}
- {{ end }} - - {{ block "contentAfterLayout" . }} + {{ end }} + + {{ block "contentAfterLayout" . }}
{{ block "contentAfter" . }}{{ end }}
- {{ end }} + {{ end }} +
{{ end }} {{ block "footerLayout" . }} -