File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/httpd/web.go
2979 lines · 93kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package httpd
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"errors"
133728e barerepo 1mo
6
"html/template"
133728e barerepo 1mo
7
"net/http"
133728e barerepo 1mo
8
"net/url"
133728e barerepo 1mo
9
"os"
133728e barerepo 1mo
10
"sort"
133728e barerepo 1mo
11
"strconv"
133728e barerepo 1mo
12
"strings"
133728e barerepo 1mo
13
"time"
133728e barerepo 1mo
14
133728e barerepo 1mo
15
"github.com/barerepo/server/internal/artifact"
133728e barerepo 1mo
16
"github.com/barerepo/server/internal/gitread"
133728e barerepo 1mo
17
"github.com/barerepo/server/internal/gitx"
133728e barerepo 1mo
18
"github.com/barerepo/server/internal/markup"
133728e barerepo 1mo
19
"github.com/barerepo/server/internal/proposal"
133728e barerepo 1mo
20
"github.com/barerepo/server/internal/repo"
133728e barerepo 1mo
21
"github.com/barerepo/server/internal/repocfg"
133728e barerepo 1mo
22
"github.com/barerepo/server/internal/run"
133728e barerepo 1mo
23
"github.com/barerepo/server/internal/search"
133728e barerepo 1mo
24
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
25
"github.com/barerepo/server/internal/thread"
133728e barerepo 1mo
26
"github.com/barerepo/server/internal/token"
133728e barerepo 1mo
27
"github.com/barerepo/server/internal/transport"
133728e barerepo 1mo
28
"github.com/barerepo/server/internal/workflow"
133728e barerepo 1mo
29
)
133728e barerepo 1mo
30
133728e barerepo 1mo
31
// serveWeb is the read-only web interface. Routes are appendix C.
133728e barerepo 1mo
32
func (s *Server) serveWeb(w http.ResponseWriter, r *http.Request) {
133728e barerepo 1mo
33
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
133728e barerepo 1mo
34
switch {
133728e barerepo 1mo
35
case r.URL.Path == "/favicon.ico":
133728e barerepo 1mo
36
// The design has no icon, and an empty answer beats a 404 in every console.
133728e barerepo 1mo
37
w.WriteHeader(http.StatusNoContent)
133728e barerepo 1mo
38
case r.URL.Path == "/":
133728e barerepo 1mo
39
s.serveLanding(w, r)
133728e barerepo 1mo
40
case r.URL.Path == "/runner.sh":
133728e barerepo 1mo
41
s.serveRunnerScript(w, r)
133728e barerepo 1mo
42
case r.URL.Path == "/runner.ps1":
133728e barerepo 1mo
43
s.serveRunnerPS1(w, r)
133728e barerepo 1mo
44
case r.URL.Path == "/runner/binary":
133728e barerepo 1mo
45
s.serveRunnerBinary(w, r)
133728e barerepo 1mo
46
case r.URL.Path == "/runner/attach" && r.Method == http.MethodPost:
133728e barerepo 1mo
47
s.serveRunnerAttach(w, r)
133728e barerepo 1mo
48
case r.URL.Path == "/runner/poll" && r.Method == http.MethodGet:
133728e barerepo 1mo
49
s.serveRunnerPoll(w, r)
133728e barerepo 1mo
50
case r.URL.Path == "/runner/log" && r.Method == http.MethodPost:
133728e barerepo 1mo
51
s.serveRunnerLog(w, r)
133728e barerepo 1mo
52
case r.URL.Path == "/runner/done" && r.Method == http.MethodPost:
133728e barerepo 1mo
53
s.serveRunnerDone(w, r)
133728e barerepo 1mo
54
case r.URL.Path == "/runner/artifact" && r.Method == http.MethodPost:
133728e barerepo 1mo
55
s.serveRunnerArtifact(w, r)
133728e barerepo 1mo
56
case r.URL.Path == "/keys" && isGet(r):
133728e barerepo 1mo
57
s.serveKeys(w, r)
133728e barerepo 1mo
58
case r.URL.Path == "/keys" && r.Method == http.MethodPost:
133728e barerepo 1mo
59
s.serveAddKey(w, r)
133728e barerepo 1mo
60
case r.URL.Path == "/gpgkeys" && r.Method == http.MethodPost:
133728e barerepo 1mo
61
s.serveAddGPGKey(w, r)
133728e barerepo 1mo
62
case r.URL.Path == "/gpgkeys/delete" && r.Method == http.MethodPost:
133728e barerepo 1mo
63
s.serveDeleteGPGKey(w, r)
133728e barerepo 1mo
64
case r.URL.Path == "/keys/delete" && r.Method == http.MethodPost:
133728e barerepo 1mo
65
s.serveDeleteKey(w, r)
133728e barerepo 1mo
66
case r.URL.Path == "/tokens" && r.Method == http.MethodPost:
133728e barerepo 1mo
67
s.serveNewToken(w, r)
133728e barerepo 1mo
68
case r.URL.Path == "/tokens/delete" && r.Method == http.MethodPost:
133728e barerepo 1mo
69
s.serveDeleteToken(w, r)
133728e barerepo 1mo
70
case r.URL.Path == "/inbox":
133728e barerepo 1mo
71
s.serveInbox(w, r)
133728e barerepo 1mo
72
case r.URL.Path == "/inbox.atom":
133728e barerepo 1mo
73
s.serveInboxFeed(w, r)
133728e barerepo 1mo
74
case r.URL.Path == "/search":
133728e barerepo 1mo
75
s.serveSearch(w, r)
133728e barerepo 1mo
76
case r.URL.Path == "/new":
133728e barerepo 1mo
77
s.serveNewRepo(w, r)
133728e barerepo 1mo
78
case r.URL.Path == "/signup":
133728e barerepo 1mo
79
s.serveSignup(w, r)
133728e barerepo 1mo
80
case r.URL.Path == "/signin" && isGet(r):
133728e barerepo 1mo
81
s.serveSignin(w, r)
133728e barerepo 1mo
82
case r.URL.Path == "/auth/challenge" && r.Method == http.MethodPost:
133728e barerepo 1mo
83
s.serveChallenge(w, r)
133728e barerepo 1mo
84
case r.URL.Path == "/auth/verify" && r.Method == http.MethodPost:
133728e barerepo 1mo
85
s.serveVerify(w, r)
133728e barerepo 1mo
86
case r.URL.Path == "/auth/claim" && isGet(r):
133728e barerepo 1mo
87
s.serveClaim(w, r)
133728e barerepo 1mo
88
case strings.HasPrefix(r.URL.Path, "/card/") && isGet(r):
133728e barerepo 1mo
89
s.serveNamedCard(w, r, strings.Split(strings.TrimPrefix(r.URL.Path, "/card/"), "/"))
133728e barerepo 1mo
90
case r.URL.Path == "/signout" && r.Method == http.MethodPost:
133728e barerepo 1mo
91
s.serveSignout(w, r)
133728e barerepo 1mo
92
case len(parts) == 1 && strings.HasSuffix(parts[0], ".keys"):
133728e barerepo 1mo
93
s.serveUserSSHKeys(w, r, strings.TrimSuffix(parts[0], ".keys"))
133728e barerepo 1mo
94
case len(parts) == 1 && strings.HasSuffix(parts[0], ".gpg"):
133728e barerepo 1mo
95
s.serveUserKeys(w, r, strings.TrimSuffix(parts[0], ".gpg"))
133728e barerepo 1mo
96
case len(parts) == 1 && strings.HasSuffix(parts[0], ".atom"):
133728e barerepo 1mo
97
s.serveUserFeed(w, r, strings.TrimSuffix(parts[0], ".atom"))
133728e barerepo 1mo
98
case len(parts) == 1:
133728e barerepo 1mo
99
s.serveProfile(w, r, parts[0])
133728e barerepo 1mo
100
case len(parts) == 2 && strings.HasSuffix(parts[1], ".atom"):
133728e barerepo 1mo
101
s.serveRepoFeed(w, r, parts[0], strings.TrimSuffix(parts[1], ".atom"))
133728e barerepo 1mo
102
case len(parts) == 2:
133728e barerepo 1mo
103
s.serveRepoLog(w, r, parts[0], parts[1])
133728e barerepo 1mo
104
case len(parts) == 3 && parts[2] == "config":
133728e barerepo 1mo
105
s.serveRepoConfig(w, r, parts[0], parts[1], "")
133728e barerepo 1mo
106
case len(parts) == 3 && (parts[2] == "rename" || parts[2] == "transfer") && r.Method == http.MethodPost:
133728e barerepo 1mo
107
s.serveRepoMove(w, r, parts[0], parts[1], parts[2])
133728e barerepo 1mo
108
case len(parts) == 3 && parts[2] == "copy" && r.Method == http.MethodPost:
133728e barerepo 1mo
109
s.serveRepoCopy(w, r, parts[0], parts[1])
133728e barerepo 1mo
110
case len(parts) == 3 && parts[2] == "delete" && r.Method == http.MethodPost:
133728e barerepo 1mo
111
s.serveRepoDelete(w, r, parts[0], parts[1])
133728e barerepo 1mo
112
case len(parts) == 3 && parts[2] == "rejected":
133728e barerepo 1mo
113
s.serveRejected(w, r, parts[0], parts[1])
133728e barerepo 1mo
114
case len(parts) == 3 && parts[2] == "releases":
133728e barerepo 1mo
115
s.serveReleases(w, r, parts[0], parts[1])
133728e barerepo 1mo
116
case len(parts) == 4 && parts[2] == "release":
133728e barerepo 1mo
117
s.serveRelease(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
118
case len(parts) == 5 && parts[2] == "release":
133728e barerepo 1mo
119
s.serveReleaseFile(w, r, parts[0], parts[1], parts[3], parts[4])
133728e barerepo 1mo
120
case len(parts) == 3 && parts[2] == "runs":
133728e barerepo 1mo
121
s.serveRuns(w, r, parts[0], parts[1])
133728e barerepo 1mo
122
case len(parts) == 4 && parts[2] == "run":
133728e barerepo 1mo
123
s.serveRun(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
124
case len(parts) == 5 && parts[2] == "run" && parts[4] == "log":
133728e barerepo 1mo
125
s.serveRunLog(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
126
case len(parts) == 5 && parts[2] == "run" && parts[4] == "rerun" && r.Method == http.MethodPost:
133728e barerepo 1mo
127
s.serveRerun(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
128
case len(parts) == 3 && parts[2] == "runners":
133728e barerepo 1mo
129
s.serveRunners(w, r, parts[0], parts[1])
133728e barerepo 1mo
130
case len(parts) == 4 && parts[2] == "runners" && (parts[3] == "new" || parts[3] == "token"):
133728e barerepo 1mo
131
s.serveRunnerSetup(w, r, parts[0], parts[1])
133728e barerepo 1mo
132
case len(parts) == 4 && parts[2] == "runners" && parts[3] == "forget" && r.Method == http.MethodPost:
133728e barerepo 1mo
133
s.serveForgetRunner(w, r, parts[0], parts[1])
133728e barerepo 1mo
134
case len(parts) == 3 && parts[2] == "threads.atom":
133728e barerepo 1mo
135
s.serveThreadsFeed(w, r, parts[0], parts[1])
133728e barerepo 1mo
136
case len(parts) == 3 && parts[2] == "threads" && r.Method == http.MethodPost:
133728e barerepo 1mo
137
s.serveNewThreadPost(w, r, parts[0], parts[1])
133728e barerepo 1mo
138
case len(parts) == 3 && parts[2] == "threads":
133728e barerepo 1mo
139
s.serveThreads(w, r, parts[0], parts[1])
133728e barerepo 1mo
140
case len(parts) == 4 && parts[2] == "threads" && parts[3] == "new":
133728e barerepo 1mo
141
s.serveNewThreadForm(w, r, parts[0], parts[1], newThreadPage{})
133728e barerepo 1mo
142
case len(parts) == 5 && parts[2] == "thread" && parts[4] == "comment":
133728e barerepo 1mo
143
s.serveLineComment(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
144
case len(parts) == 5 && parts[2] == "thread" && parts[4] == "close" && r.Method == http.MethodPost:
133728e barerepo 1mo
145
s.serveThreadState(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
146
case len(parts) == 5 && parts[2] == "thread" && parts[4] == "reply" && r.Method == http.MethodPost:
133728e barerepo 1mo
147
s.serveReply(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
148
case len(parts) == 4 && parts[2] == "thread":
133728e barerepo 1mo
149
s.serveThread(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
150
case len(parts) == 4 && parts[2] == "commit":
133728e barerepo 1mo
151
s.serveCommit(w, r, parts[0], parts[1], parts[3])
133728e barerepo 1mo
152
case len(parts) >= 3 && parts[2] == "compare":
133728e barerepo 1mo
153
spec := ""
133728e barerepo 1mo
154
if len(parts) > 3 {
133728e barerepo 1mo
155
spec = strings.Join(parts[3:], "/")
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
s.serveCompare(w, r, parts[0], parts[1], spec)
133728e barerepo 1mo
158
case len(parts) >= 5 && parts[2] == "raw":
133728e barerepo 1mo
159
s.serveRaw(w, r, parts[0], parts[1], parts[3], strings.Join(parts[4:], "/"))
133728e barerepo 1mo
160
case len(parts) == 3 && parts[2] == "readme":
133728e barerepo 1mo
161
s.serveReadme(w, r, parts[0], parts[1])
133728e barerepo 1mo
162
case len(parts) >= 5 && parts[2] == "file":
133728e barerepo 1mo
163
s.serveFile(w, r, parts[0], parts[1], parts[3], strings.Join(parts[4:], "/"))
133728e barerepo 1mo
164
case len(parts) >= 3 && parts[2] == "files":
133728e barerepo 1mo
165
// /files, /files/<ref>, /files/<ref>/<path...>
133728e barerepo 1mo
166
ref, path := "", ""
133728e barerepo 1mo
167
if len(parts) > 3 {
133728e barerepo 1mo
168
ref = parts[3]
133728e barerepo 1mo
169
}
133728e barerepo 1mo
170
if len(parts) > 4 {
133728e barerepo 1mo
171
path = strings.Join(parts[4:], "/")
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
s.serveFiles(w, r, parts[0], parts[1], ref, path)
133728e barerepo 1mo
174
default:
133728e barerepo 1mo
175
s.notFound(w, r)
133728e barerepo 1mo
176
}
133728e barerepo 1mo
177
}
133728e barerepo 1mo
178
133728e barerepo 1mo
179
// visibilityWord is the value the pasted config carries, and only the exact word opens a repository.
133728e barerepo 1mo
180
func visibilityWord(public bool) string {
133728e barerepo 1mo
181
if public {
133728e barerepo 1mo
182
return "public"
133728e barerepo 1mo
183
}
133728e barerepo 1mo
184
return "private"
133728e barerepo 1mo
185
}
133728e barerepo 1mo
186
133728e barerepo 1mo
187
// oneLine keeps a pasted heredoc to one line of prose, because a newline in it would end the block.
133728e barerepo 1mo
188
func oneLine(s string) string {
133728e barerepo 1mo
189
s = strings.ReplaceAll(s, "\r", " ")
133728e barerepo 1mo
190
s = strings.ReplaceAll(s, "\n", " ")
133728e barerepo 1mo
191
return strings.TrimSpace(s)
133728e barerepo 1mo
192
}
133728e barerepo 1mo
193
133728e barerepo 1mo
194
// serveLanding is the root: sign-in when signed out, per appendix C, and rule 7 lists nothing.
133728e barerepo 1mo
195
func (s *Server) serveLanding(w http.ResponseWriter, r *http.Request) {
133728e barerepo 1mo
196
if who := s.viewer(r); who != "" {
133728e barerepo 1mo
197
http.Redirect(w, r, "/"+who, http.StatusFound)
133728e barerepo 1mo
198
return
133728e barerepo 1mo
199
}
133728e barerepo 1mo
200
http.Redirect(w, r, "/signin", http.StatusFound)
133728e barerepo 1mo
201
}
133728e barerepo 1mo
202
133728e barerepo 1mo
203
// notFound names what does exist near the path, per chapter 24, because the server knows.
133728e barerepo 1mo
204
func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
204 of 2979 lines · the whole file
history · rawbarerepo 0.1.0