diff --git a/appview/db/db.go b/appview/db/db.go index 9b659f32..e3a07df2 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -47,6 +47,7 @@ func Make(dbPath string) (*DB, error) { name text not null, knot text not null, rkey text not null, + at_uri text not null unique, created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), unique(did, name, knot, rkey) ); @@ -59,15 +60,44 @@ func Make(dbPath string) (*DB, error) { create table if not exists follows ( user_did text not null, subject_did text not null, + at_uri text not null unique, rkey text not null, followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), primary key (user_did, subject_did), check (user_did <> subject_did) ); + create table if not exists issues ( + id integer primary key autoincrement, + owner_did text not null, + repo_at text not null, + issue_id integer not null unique, + title text not null, + body text not null, + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + unique(repo_at, issue_id), + foreign key (repo_at) references repos(at_uri) on delete cascade + ); + create table if not exists comments ( + id integer primary key autoincrement, + owner_did text not null, + issue_id integer not null, + repo_at text not null, + comment_id integer not null, + body text not null, + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + unique(issue_id, comment_id), + foreign key (issue_id) references issues(issue_id) on delete cascade + ); create table if not exists _jetstream ( id integer primary key autoincrement, last_time_us integer not null ); + + create table if not exists repo_issue_seqs ( + repo_at text primary key, + next_issue_id integer not null default 1 + ); + `) if err != nil { return nil, err diff --git a/appview/db/issues.go b/appview/db/issues.go new file mode 100644 index 00000000..bdc64068 --- /dev/null +++ b/appview/db/issues.go @@ -0,0 +1,179 @@ +package db + +import "time" + +type Issue struct { + RepoAt string + OwnerDid string + IssueId int + Created *time.Time + Title string + Body string + Open bool +} + +type Comment struct { + OwnerDid string + RepoAt string + Issue int + CommentId int + Body string + Created *time.Time +} + +func (d *DB) NewIssue(issue *Issue) (int, error) { + tx, err := d.db.Begin() + if err != nil { + return 0, err + } + defer tx.Rollback() + + _, err = tx.Exec(` + insert or ignore into repo_issue_seqs (repo_at, next_issue_id) + values (?, 1) + `, issue.RepoAt) + if err != nil { + return 0, err + } + + var nextId int + err = tx.QueryRow(` + update repo_issue_seqs + set next_issue_id = next_issue_id + 1 + where repo_at = ? + returning next_issue_id - 1 + `, issue.RepoAt).Scan(&nextId) + if err != nil { + return 0, err + } + + issue.IssueId = nextId + + _, err = tx.Exec(` + insert into issues (repo_at, owner_did, issue_id, title, body) + values (?, ?, ?, ?, ?) + `, issue.RepoAt, issue.OwnerDid, issue.IssueId, issue.Title, issue.Body) + if err != nil { + return 0, err + } + + if err := tx.Commit(); err != nil { + return 0, err + } + + return nextId, nil +} + +func (d *DB) GetIssues(repoAt string) ([]Issue, error) { + var issues []Issue + + rows, err := d.db.Query(`select owner_did, issue_id, created, title, body, open from issues where repo_at = ?`, repoAt) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var issue Issue + var createdAt string + err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open) + if err != nil { + return nil, err + } + + createdTime, err := time.Parse(time.RFC3339, createdAt) + if err != nil { + return nil, err + } + issue.Created = &createdTime + + issues = append(issues, issue) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return issues, nil +} + +func (d *DB) GetIssueWithComments(repoAt string, issueId int) (*Issue, []Comment, error) { + query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?` + row := d.db.QueryRow(query, repoAt, issueId) + + var issue Issue + var createdAt string + err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open) + if err != nil { + return nil, nil, err + } + + createdTime, err := time.Parse(time.RFC3339, createdAt) + if err != nil { + return nil, nil, err + } + issue.Created = &createdTime + + comments, err := d.GetComments(repoAt, issueId) + if err != nil { + return nil, nil, err + } + + return &issue, comments, nil +} + +func (d *DB) NewComment(comment *Comment) error { + query := `insert into comments (owner_did, repo_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?)` + _, err := d.db.Exec( + query, + comment.OwnerDid, + comment.RepoAt, + comment.Issue, + comment.CommentId, + comment.Body, + ) + return err +} + +func (d *DB) GetComments(repoAt string, issueId int) ([]Comment, error) { + var comments []Comment + + rows, err := d.db.Query(`select owner_did, issue_id, comment_id, body, created from comments where repo_at = ? and issue_id = ? order by created asc`, repoAt, issueId) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var comment Comment + var createdAt string + err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.Body, &createdAt) + if err != nil { + return nil, err + } + + createdAtTime, err := time.Parse(time.RFC3339, createdAt) + if err != nil { + return nil, err + } + comment.Created = &createdAtTime + + comments = append(comments, comment) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return comments, nil +} + +func (d *DB) CloseIssue(repoAt string, issueId int) error { + _, err := d.db.Exec(`update issues set open = 0 where repo_at = ? and issue_id = ?`, repoAt, issueId) + return err +} + +func (d *DB) ReopenIssue(repoAt string, issueId int) error { + _, err := d.db.Exec(`update issues set open = 1 where repo_at = ? and issue_id = ?`, repoAt, issueId) + return err +} diff --git a/appview/db/repos.go b/appview/db/repos.go index a92f6f7c..90cdc7e6 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -11,6 +11,7 @@ type Repo struct { Knot string Rkey string Created time.Time + AtUri string } func (d *DB) GetAllRepos() ([]Repo, error) { @@ -66,10 +67,10 @@ func (d *DB) GetAllReposByDid(did string) ([]Repo, error) { func (d *DB) GetRepo(did, name string) (*Repo, error) { var repo Repo - row := d.db.QueryRow(`select did, name, knot, created from repos where did = ? and name = ?`, did, name) + row := d.db.QueryRow(`select did, name, knot, created, at_uri from repos where did = ? and name = ?`, did, name) var createdAt string - if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt); err != nil { + if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri); err != nil { return nil, err } createdAtTime, _ := time.Parse(time.RFC3339, createdAt) @@ -79,7 +80,7 @@ func (d *DB) GetRepo(did, name string) (*Repo, error) { } func (d *DB) AddRepo(repo *Repo) error { - _, err := d.db.Exec(`insert into repos (did, name, knot, rkey) values (?, ?, ?, ?)`, repo.Did, repo.Name, repo.Knot, repo.Rkey) + _, err := d.db.Exec(`insert into repos (did, name, knot, rkey, at_uri) values (?, ?, ?, ?, ?)`, repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri) return err } diff --git a/appview/pages/pages.go b/appview/pages/pages.go index a91559ed..3835eef2 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -283,6 +283,9 @@ type RepoIndexParams struct { func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error { params.Active = "overview" + if params.IsEmpty { + return p.executeRepo("repo/empty", w, params) + } return p.executeRepo("repo/index", w, params) } @@ -429,6 +432,50 @@ func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { return p.executeRepo("repo/settings", w, params) } +type RepoIssuesParams struct { + LoggedInUser *auth.User + RepoInfo RepoInfo + Active string + Issues []db.Issue +} + +func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { + params.Active = "issues" + return p.executeRepo("repo/issues", w, params) +} + +type RepoSingleIssueParams struct { + LoggedInUser *auth.User + RepoInfo RepoInfo + Active string + Issue db.Issue + Comments []db.Comment + IssueOwnerHandle string + + State string +} + +func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { + params.Active = "issues" + if params.Issue.Open { + params.State = "open" + } else { + params.State = "closed" + } + return p.execute("repo/issue", w, params) +} + +type RepoNewIssueParams struct { + LoggedInUser *auth.User + RepoInfo RepoInfo + Active string +} + +func (p *Pages) RepoNewIssue(w io.Writer, params RepoNewIssueParams) error { + params.Active = "issues" + return p.executeRepo("repo/new-issue", w, params) +} + func (p *Pages) Static() http.Handler { sub, err := fs.Sub(files, "static") if err != nil { diff --git a/appview/pages/templates/repo/empty.html b/appview/pages/templates/repo/empty.html index 0ba2a347..c8a0807a 100644 --- a/appview/pages/templates/repo/empty.html +++ b/appview/pages/templates/repo/empty.html @@ -1,7 +1,7 @@ -{{ define "title" }}{{ .RepoInfo.OwnerWithAt }} / {{ .RepoInfo.Name }}{{ end }} - -{{ define "content" }} +{{ define "repoContent" }}
-

This is an empty Git repository. Push some commits here.

+

+ This is an empty repository. Push some commits here. +

{{ end }} diff --git a/appview/pages/templates/repo/index.html b/appview/pages/templates/repo/index.html index b482074b..91c8f902 100644 --- a/appview/pages/templates/repo/index.html +++ b/appview/pages/templates/repo/index.html @@ -1,95 +1,103 @@ {{ define "repoContent" }} -
- {{- if .IsEmpty }} - this repo is empty - {{ else }} -
-
- {{ $containerstyle := "py-1" }} - {{ $linkstyle := "no-underline hover:underline" }} +
+
+
+ {{ $containerstyle := "py-1" }} + {{ $linkstyle := "no-underline hover:underline" }} -
- -
- {{ range .Files }} - {{ if not .IsFile }} -
-
- -
- {{ .Name }} -
-
- - -
-
- {{ end }} - {{ end }} +
+ +
- {{ range .Files }} - {{ if .IsFile }} -
-
- -
- {{ .Name }} -
-
+ {{ range .Files }} + {{ if not .IsFile }} +
+
+ +
+ {{ .Name }} +
+
- -
-
- {{ end }} - {{ end }} -
-
- {{ range .Commits }} -
{{ timeFmt .LastCommit.Author.When }} +
+
+ {{ end }} + {{ end }} + + {{ range .Files }} + {{ if .IsFile }} +
+
+ +
+ {{ .Name }} +
+
+ + +
+
+ {{ end }} + {{ end }} +
+
+ {{ range .Commits }} +
+ " + > +
+ {{ $messageParts := splitN .Message "\n\n" 2 }} +
+
+
+ {{ index $messageParts 0 }} + {{ if gt (len $messageParts) 1 }} -
- {{ $messageParts := splitN .Message "\n\n" 2 }} -
-
-
- {{ index $messageParts 0 }} - {{ if gt (len $messageParts) 1 }} + + {{ end }} +
+ {{ if gt (len $messageParts) 1 }} + + {{ end }} +
+
+
- +
+ + {{ slice .Hash.String 0 8 }} + + + + {{ .Author.Name }} + +
+ {{ timeFmt .Author.When }} +
+
{{ end }} -
- {{ if gt (len $messageParts) 1 }} - - {{ end }}
-
- -
- - {{ slice .Hash.String 0 8 }} - - - - {{ .Author.Name }} - -
- {{ timeFmt .Author.When }} -
-
- {{ end }} -
-
- {{- end -}} - -
+
{{ end }} {{ define "repoAfter" }} -{{- if .Readme }} -
-
- {{- .Readme -}} -
-
-{{- end -}} + {{- if .Readme }} +
+
+ {{- .Readme -}} +
+
+ {{- end -}} + -
- clone -
 git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }} 
-
+
+ clone +
+ git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }} 
+
{{ end }} diff --git a/appview/pages/templates/repo/issue.html b/appview/pages/templates/repo/issue.html new file mode 100644 index 00000000..5b9e86f4 --- /dev/null +++ b/appview/pages/templates/repo/issue.html @@ -0,0 +1,107 @@ +{{ define "title" }} + {{ .Issue.Title }} · + {{ .RepoInfo.FullName }} +{{ end }} + +{{ define "repoContent" }} +
+

+ {{ .Issue.Title }} + #{{ .Issue.IssueId }} +

+ + +
+ + {{ $bgColor := "bg-gray-800" }} + {{ $icon := "ban" }} + {{ if eq .State "open" }} + {{ $bgColor = "bg-green-600" }} + {{ $icon = "circle-dot" }} + {{ end }} + + +
+
+
+ + {{ .State }} +
+ + opened by + {{ didOrHandle .Issue.OwnerDid .IssueOwnerHandle }} + +
+ + {{ if .Issue.Body }} +
+ {{ .Issue.Body | escapeHtml }} +
+ {{ end }} +
+ +
+ {{ range .Comments }} +
+
+ + {{ .OwnerDid }} + + + {{ .Created | timeFmt }} + +
+
+ {{ nl2br .Body }} +
+
+ {{ end }} +
+ + {{ if .LoggedInUser }} +
+ + +
+
+ {{ end }} + + {{ if eq .LoggedInUser.Did .Issue.OwnerDid }} + {{ $action := "close" }} + {{ $icon := "circle-x" }} + {{ $hoverColor := "red" }} + {{ if eq .State "closed" }} + {{ $action = "reopen" }} + {{ $icon = "circle-dot" }} + {{ $hoverColor = "green" }} + {{ end }} +
+ +
+ {{ end }} +{{ end }} diff --git a/appview/pages/templates/repo/issues.html b/appview/pages/templates/repo/issues.html new file mode 100644 index 00000000..66137b57 --- /dev/null +++ b/appview/pages/templates/repo/issues.html @@ -0,0 +1,48 @@ +{{ define "title" }}issues | {{ .RepoInfo.FullName }}{{ end }} + +{{ define "repoContent" }} +
+

issues

+
+ + + new issue + +
+ +
+ {{ range .Issues }} +
+ +
+ {{ if .Open }} + + {{ else }} + + {{ end }} + + {{ .Title }} + +
+
+ #{{ .IssueId }} + + opened by + {{ .OwnerDid }} + +
+
+ {{ end }} +
+{{ end }} diff --git a/appview/pages/templates/repo/new-issue.html b/appview/pages/templates/repo/new-issue.html new file mode 100644 index 00000000..fcc84e7e --- /dev/null +++ b/appview/pages/templates/repo/new-issue.html @@ -0,0 +1,30 @@ +{{ define "title" }}new issue | {{ .RepoInfo.FullName }}{{ end }} + +{{ define "repoContent" }} +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+{{ end }} diff --git a/appview/state/middleware.go b/appview/state/middleware.go index bb874901..bbb16c2a 100644 --- a/appview/state/middleware.go +++ b/appview/state/middleware.go @@ -187,6 +187,7 @@ func ResolveRepoKnot(s *State) Middleware { } ctx := context.WithValue(req.Context(), "knot", repo.Knot) + ctx = context.WithValue(ctx, "repoAt", repo.AtUri) next.ServeHTTP(w, req.WithContext(ctx)) }) } diff --git a/appview/state/repo.go b/appview/state/repo.go index 24396cee..ee79e4e3 100644 --- a/appview/state/repo.go +++ b/appview/state/repo.go @@ -6,14 +6,17 @@ import ( "fmt" "io" "log" + "math/rand/v2" "net/http" "path" + "strconv" "strings" "github.com/bluesky-social/indigo/atproto/identity" securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-chi/chi/v5" "github.com/sotangled/tangled/appview/auth" + "github.com/sotangled/tangled/appview/db" "github.com/sotangled/tangled/appview/pages" "github.com/sotangled/tangled/types" ) @@ -441,6 +444,7 @@ type FullyResolvedRepo struct { Knot string OwnerId identity.Identity RepoName string + RepoAt string } func (f *FullyResolvedRepo) OwnerDid() string { @@ -500,6 +504,234 @@ func (f *FullyResolvedRepo) Collaborators(ctx context.Context, s *State) ([]page return collaborators, nil } +func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + issueId := chi.URLParam(r, "issue") + issueIdInt, err := strconv.Atoi(issueId) + if err != nil { + http.Error(w, "bad issue id", http.StatusBadRequest) + log.Println("failed to parse issue id", err) + return + } + + issue, comments, err := s.db.GetIssueWithComments(f.RepoAt, issueIdInt) + if err != nil { + log.Println("failed to get issue and comments", err) + s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") + return + } + + issueOwnerIdent, err := s.resolver.ResolveIdent(r.Context(), issue.OwnerDid) + if err != nil { + log.Println("failed to resolve issue owner", err) + } + + s.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ + LoggedInUser: user, + RepoInfo: pages.RepoInfo{ + OwnerDid: f.OwnerDid(), + OwnerHandle: f.OwnerHandle(), + Name: f.RepoName, + SettingsAllowed: settingsAllowed(s, user, f), + }, + Issue: *issue, + Comments: comments, + + IssueOwnerHandle: issueOwnerIdent.Handle.String(), + }) + +} + +func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + issueId := chi.URLParam(r, "issue") + issueIdInt, err := strconv.Atoi(issueId) + if err != nil { + http.Error(w, "bad issue id", http.StatusBadRequest) + log.Println("failed to parse issue id", err) + return + } + + if user.Did == f.OwnerDid() { + err := s.db.CloseIssue(f.RepoAt, issueIdInt) + if err != nil { + log.Println("failed to close issue", err) + s.pages.Notice(w, "issues", "Failed to close issue. Try again later.") + return + } + s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt)) + return + } else { + log.Println("user is not the owner of the repo") + http.Error(w, "for biden", http.StatusUnauthorized) + return + } +} + +func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + issueId := chi.URLParam(r, "issue") + issueIdInt, err := strconv.Atoi(issueId) + if err != nil { + http.Error(w, "bad issue id", http.StatusBadRequest) + log.Println("failed to parse issue id", err) + return + } + + if user.Did == f.OwnerDid() { + err := s.db.ReopenIssue(f.RepoAt, issueIdInt) + if err != nil { + log.Println("failed to reopen issue", err) + s.pages.Notice(w, "issues", "Failed to reopen issue. Try again later.") + return + } + s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt)) + return + } else { + log.Println("user is not the owner of the repo") + http.Error(w, "forbidden", http.StatusUnauthorized) + return + } +} + +func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + issueId := chi.URLParam(r, "issue") + issueIdInt, err := strconv.Atoi(issueId) + if err != nil { + http.Error(w, "bad issue id", http.StatusBadRequest) + log.Println("failed to parse issue id", err) + return + } + + switch r.Method { + case http.MethodPost: + body := r.FormValue("body") + if body == "" { + s.pages.Notice(w, "issue", "Body is required") + return + } + + commentId := rand.IntN(1000000) + fmt.Println(commentId) + fmt.Println("comment id", commentId) + + err := s.db.NewComment(&db.Comment{ + OwnerDid: user.Did, + RepoAt: f.RepoAt, + Issue: issueIdInt, + CommentId: commentId, + Body: body, + }) + if err != nil { + log.Println("failed to create comment", err) + s.pages.Notice(w, "issue-comment", "Failed to create comment.") + return + } + + s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId)) + return + } +} + +func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + issues, err := s.db.GetIssues(f.RepoAt) + if err != nil { + log.Println("failed to get issues", err) + s.pages.Notice(w, "issues", "Failed to load issues. Try again later.") + return + } + + s.pages.RepoIssues(w, pages.RepoIssuesParams{ + LoggedInUser: s.auth.GetUser(r), + RepoInfo: pages.RepoInfo{ + OwnerDid: f.OwnerDid(), + OwnerHandle: f.OwnerHandle(), + Name: f.RepoName, + SettingsAllowed: settingsAllowed(s, user, f), + }, + Issues: issues, + }) + return +} + +func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { + user := s.auth.GetUser(r) + + f, err := fullyResolvedRepo(r) + if err != nil { + log.Println("failed to get repo and knot", err) + return + } + + switch r.Method { + case http.MethodGet: + s.pages.RepoNewIssue(w, pages.RepoNewIssueParams{ + LoggedInUser: user, + RepoInfo: pages.RepoInfo{ + Name: f.RepoName, + OwnerDid: f.OwnerDid(), + OwnerHandle: f.OwnerHandle(), + SettingsAllowed: settingsAllowed(s, user, f), + }, + }) + case http.MethodPost: + title := r.FormValue("title") + body := r.FormValue("body") + + if title == "" || body == "" { + s.pages.Notice(w, "issue", "Title and body are required") + return + } + + issueId, err := s.db.NewIssue(&db.Issue{ + RepoAt: f.RepoAt, + Title: title, + Body: body, + OwnerDid: user.Did, + }) + if err != nil { + log.Println("failed to create issue", err) + s.pages.Notice(w, "issue", "Failed to create issue.") + return + } + + s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueId)) + return + } +} + func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { repoName := chi.URLParam(r, "repo") knot, ok := r.Context().Value("knot").(string) @@ -513,10 +745,17 @@ func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { return nil, fmt.Errorf("malformed middleware") } + repoAt, ok := r.Context().Value("repoAt").(string) + if !ok { + log.Println("malformed middleware") + return nil, fmt.Errorf("malformed middleware") + } + return &FullyResolvedRepo{ Knot: knot, OwnerId: id, RepoName: repoName, + RepoAt: repoAt, }, nil } diff --git a/appview/state/state.go b/appview/state/state.go index 0c714899..0edd6dfc 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -70,7 +70,6 @@ func Make() (*State, error) { } did := e.Did - fmt.Println("got event", e.Commit.Collection, e.Commit.RKey, e.Commit.Record) raw := json.RawMessage(e.Commit.Record) switch e.Commit.Collection { @@ -597,6 +596,8 @@ func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) { } log.Println("created repo record: ", atresp.Uri) + repo.AtUri = atresp.Uri + err = s.db.AddRepo(repo) if err != nil { log.Println(err) @@ -802,6 +803,16 @@ func (s *State) UserRouter() http.Handler { r.Get("/tags", s.RepoTags) r.Get("/blob/{ref}/*", s.RepoBlob) + r.Route("/issues", func(r chi.Router) { + r.Get("/", s.RepoIssues) + r.Get("/{issue}", s.RepoSingleIssue) + r.Get("/new", s.NewIssue) + r.Post("/new", s.NewIssue) + r.Post("/{issue}/comment", s.IssueComment) + r.Post("/{issue}/close", s.CloseIssue) + r.Post("/{issue}/reopen", s.ReopenIssue) + }) + // These routes get proxied to the knot r.Get("/info/refs", s.InfoRefs) r.Post("/git-upload-pack", s.UploadPack) diff --git a/go.mod b/go.mod index 4e1525ce..ab5c6678 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 github.com/sethvargo/go-envconfig v1.1.0 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 ) diff --git a/go.sum b/go.sum index 32fdd9b3..526798aa 100644 --- a/go.sum +++ b/go.sum @@ -307,6 +307,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=