💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › 22315ec19b3b27a546c5cc0bef… captured on 2023-01-29 at 16:00:27. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 package gmi
1
2 import (
3 "bytes"
4 "errors"
5 "fmt"
6 "gemigit/access"
7 "gemigit/config"
8 "gemigit/db"
9 "gemigit/repo"
10 "io"
11 "log"
12 "strconv"
13 "strings"
14 "text/template"
15
16 "github.com/go-git/go-git/v5/plumbing"
17 "github.com/go-git/go-git/v5/plumbing/object"
18 "github.com/go-git/go-git/v5/plumbing/transport"
19 "github.com/pitr/gig"
20 )
21
22 func execT(c gig.Context, template string, data interface{}) error {
23 t := templates.Lookup(template)
24 var b bytes.Buffer
25 err := t.Execute(&b, data)
26 if err != nil {
27 log.Println(err.Error())
28 return c.NoContent(gig.StatusTemporaryFailure, err.Error())
29 }
30 return c.Gemini(b.String())
31 }
32
33 func execTemplate(template string, data interface{}) (string, error) {
34 t := templates.Lookup(template)
35 var b bytes.Buffer
36 err := t.Execute(&b, data)
37 if err != nil {
38 log.Println(err.Error())
39 return "", err
40 }
41 return strings.TrimRight(b.String(), "\n"), nil
42 }
43
44 const (
45 pageLog = iota
46 pageFiles
47 pageRefs
48 pageLicense
49 pageReadme
50 )
51
52 var templates *template.Template
53
54 func LoadTemplate(dir string) error {
55 var err error
56
57 templates = template.New("gmi")
58 template.Must(templates.Funcs(template.FuncMap {
59 "AccessFirst": accessFirstOption,
60 "AccessSecond": accessSecondOption,
61 "AccessPrivilege": privilegeToString,
62 }).ParseFiles(
63 dir + "/index.gmi",
64 dir + "/account.gmi",
65 dir + "/repo.gmi",
66 dir + "/repo_log.gmi",
67 dir + "/repo_files.gmi",
68 dir + "/repo_refs.gmi",
69 dir + "/repo_license.gmi",
70 dir + "/repo_readme.gmi",
71 dir + "/repo_access.gmi",
72 dir + "/public_repo.gmi",
73 dir + "/group_list.gmi",
74 dir + "/group.gmi",
75 dir + "/public_list.gmi",
76 dir + "/public_user.gmi",
77 ))
78 if err != nil {
79 return err
80 }
81 log.Println("Templates loaded")
82 return nil
83 }
84
85 func showRepoFile(user string, reponame string, file string) (string, error) {
86 out, err := repo.GetFile(reponame, user, file)
87 if err != nil {
88 return "", err
89 }
90 reader, err := out.Reader()
91 if err != nil {
92 return "", err
93 }
94 buf, err := io.ReadAll(reader)
95 if err != nil {
96 return "", err
97 }
98 return string(buf), nil
99 }
100
101 func ShowIndex(c gig.Context) (error) {
102 _, connected := db.GetUser(c.CertHash())
103 data := struct {
104 Title string
105 Registration bool
106 Connected bool
107 }{
108 Title: config.Cfg.Title,
109 Registration: config.Cfg.Users.Registration,
110 Connected: connected,
111 }
112 return execT(c, "index.gmi", data)
113 }
114
115 func ShowAccount(c gig.Context) (error) {
116 user, exist := db.GetUser(c.CertHash())
117 if !exist {
118 return c.NoContent(gig.StatusBadRequest,
119 "Invalid username")
120 }
121 repoNames := []string{}
122 repos, err := user.GetRepos(false)
123 if err != nil {
124 repoNames = []string{"Failed to load repositories"}
125 log.Println(err)
126 } else {
127 for _, repo := range repos {
128 repoNames = append(repoNames, repo.Name)
129 }
130 }
131 accessRepos, err := db.HasReadAccessTo(user.ID)
132 data := struct {
133 Username string
134 Description string
135 Repositories []string
136 RepositoriesAccess []db.Repo
137 }{
138 Username: user.Name,
139 Description: user.Description,
140 Repositories: repoNames,
141 RepositoriesAccess: accessRepos,
142 }
143 return execT(c, "account.gmi", data)
144 }
145
146 func ShowGroups(c gig.Context) (error) {
147 user, exist := db.GetUser(c.CertHash())
148 if !exist {
149 return c.NoContent(gig.StatusBadRequest,
150 "Invalid username")
151 }
152 groups, err := user.GetGroups()
153 if err != nil {
154 log.Println(err.Error())
155 return c.NoContent(gig.StatusTemporaryFailure,
156 "Failed to fetch groups")
157 }
158 data := struct {
159 Groups []db.Group
160 }{
161 Groups: groups,
162 }
163 return execT(c, "group_list.gmi", data)
164 }
165
166 func ShowMembers(c gig.Context) (error) {
167 user, exist := db.GetUser(c.CertHash())
168 if !exist {
169 return c.NoContent(gig.StatusBadRequest,
170 "Invalid username")
171 }
172 group := c.Param("group")
173 isOwner, err := user.IsInGroup(group)
174 if err != nil {
175 return c.NoContent(gig.StatusTemporaryFailure,
176 "Group not found")
177 }
178
179 members, err := user.GetMembers(group)
180 if err != nil {
181 log.Println(err.Error())
182 return c.NoContent(gig.StatusTemporaryFailure,
183 "Failed to fetch group members")
184 }
185 desc, err := db.GetGroupDesc(group)
186 if err != nil {
187 log.Println(err.Error())
188 return c.NoContent(gig.StatusTemporaryFailure,
189 "Failed to fetch group description")
190 }
191
192 owner := ""
193 if isOwner {
194 owner = user.Name
195 } else {
196 m, err := db.GetGroupOwner(group)
197 if err != nil {
198 log.Println(err.Error())
199 return c.NoContent(gig.StatusTemporaryFailure,
200 "Failed to fetch group owner")
201 }
202 owner = m.Name
203 }
204
205 data := struct {
206 Members []db.Member
207 MembersCount int
208 IsOwner bool
209 Owner string
210 Group string
211 Description string
212 }{
213 Members: members,
214 MembersCount: len(members),
215 IsOwner: isOwner,
216 Owner: owner,
217 Group: group,
218 Description: desc,
219 }
220 return execT(c, "group.gmi", data)
221 }
222
223 func getRepo(c gig.Context, owner bool) (string, string, error) {
224 username := ""
225 if owner {
226 user, exist := db.GetUser(c.CertHash())
227 if !exist {
228 return "", "", c.NoContent(gig.StatusBadRequest,
229 "Invalid username")
230 }
231 username = user.Name
232 } else {
233 username = c.Param("user")
234 ret, err := db.IsRepoPublic(c.Param("repo"), c.Param("user"))
235 if !ret {
236 user, exist := db.GetUser(c.CertHash())
237 if exist {
238 err := access.HasReadAccess(c.Param("repo"),
239 c.Param("user"),
240 user.Name)
241 ret = err == nil
242 }
243 }
244 if !ret || err != nil {
245 return "", "", c.NoContent(gig.StatusBadRequest,
246 "No repository called " + c.Param("repo") +
247 " by user " + c.Param("user"))
248 }
249 }
250 return username, c.Param("repo"), nil
251 }
252
253 func hasFile(name string, author string, file string) bool {
254 ret, err := repo.GetFile(name, author, file)
255 if ret != nil && err == nil {
256 return true
257 }
258 return false
259 }
260
261 type commit struct {
262 Message string
263 Info string
264 }
265
266 type file struct {
267 Hash string
268 Info string
269 }
270
271 type branch struct {
272 Name string
273 Info string
274 }
275
276 func showRepoLogs(name string, author string) (string, error) {
277 ret, err := repo.GetCommits(name, author)
278 if ret == nil || err == transport.ErrEmptyRemoteRepository {
279 return "", nil
280 }
281 if err != nil {
282 log.Println(err.Error())
283 return "", errors.New("Corrupted repository")
284 }
285 commits := []commit{}
286 err = ret.ForEach(func(c *object.Commit) error {
287 info := c.Hash.String() + ", by " + c.Author.Name + " on " +
288 c.Author.When.Format("2006-01-02 15:04:05")
289 commits = append(commits, commit{Info: info,
290 Message: c.Message})
291 return nil
292 })
293 return execTemplate("repo_log.gmi", commits)
294 }
295
296 func showRepoFiles(name string, author string) (string, error) {
297 ret, err := repo.GetFiles(name, author)
298 if ret == nil || err == transport.ErrEmptyRemoteRepository {
299 return "", nil
300 }
301 if err != nil {
302 log.Println(err.Error())
303 return "", errors.New("Corrupted repository")
304 }
305 files := []file{}
306 err = ret.ForEach(func(f *object.File) error {
307 info := f.Mode.String() + " " + f.Name +
308 " " + strconv.Itoa(int(f.Size))
309 files = append(files, file{Info: info,
310 Hash: f.Blob.Hash.String()})
311 return nil
312 })
313 return execTemplate("repo_files.gmi", files)
314 }
315
316 func showRepoRefs(name string, author string) (string, error) {
317 refs, err := repo.GetRefs(name, author)
318 if refs == nil || err == transport.ErrEmptyRemoteRepository {
319 return "", nil
320 }
321 if err != nil {
322 log.Println(err)
323 return "", errors.New("Corrupted repository")
324 }
325 branches := []branch{}
326 tags := []branch{}
327 err = refs.ForEach(func(c *plumbing.Reference) error {
328 if c.Type().String() != "hash-reference" ||
329 c.Name().IsRemote() {
330 return nil
331 }
332 var b branch
333 b.Name = c.Name().String()
334 b.Name = b.Name[strings.LastIndex(b.Name, "/") + 1:]
335 b.Info = "last commit on "
336
337 commit, err := repo.GetCommit(name, author, c.Hash())
338 if err != nil {
339 b.Info = "failed to fetch commit"
340 } else {
341 when := commit.Author.When
342 str := fmt.Sprintf(
343 "%d-%02d-%02d %02d:%02d:%02d",
344 when.Year(), int(when.Month()),
345 when.Day(), when.Hour(),
346 when.Minute(), when.Second())
347 b.Info += str + " by " + commit.Author.Name
348 }
349 if c.Name().IsBranch() {
350 branches = append(branches, b)
351 } else {
352 tags = append(tags, b)
353 }
354 return nil
355 })
356 refs.Close()
357 data := struct {
358 Branches []branch
359 Tags []branch
360 }{
361 branches,
362 tags,
363 }
364 return execTemplate("repo_refs.gmi", data)
365 }
366
367 func showRepoLicense(name string, author string) (string, error) {
368 content, err := showRepoFile(author, name, "LICENSE")
369 if err != nil {
370 return "", errors.New("No license found")
371 }
372 return execTemplate("repo_license.gmi", content)
373 }
374
375 func showRepoReadme(name string, author string) (string, error) {
376 content, err := showRepoFile(author, name, "README.gmi")
377 if err != nil {
378 content, err = showRepoFile(author, name, "README")
379 }
380 if err != nil {
381 return "", errors.New("No readme found")
382 }
383 return execTemplate("repo_readme.gmi", content)
384 }
385
386 func showRepo(c gig.Context, page int, owner bool) (error) {
387 author, name, err := getRepo(c, owner)
388 if err != nil {
389 log.Println(err.Error())
390 return c.NoContent(gig.StatusTemporaryFailure, err.Error())
391 }
392 desc, err := db.GetRepoDesc(name, author)
393 if err != nil {
394 log.Println(err.Error())
395 return c.NoContent(gig.StatusTemporaryFailure,
396 "Repository not found")
397 }
398 protocol := "http"
399 if config.Cfg.Git.Https {
400 protocol = "https"
401 }
402 public, err := db.IsRepoPublic(name, author)
403 if err != nil {
404 log.Println(err.Error())
405 return c.NoContent(gig.StatusTemporaryFailure,
406 "Repository not found")
407 }
408
409 content := ""
410 switch page {
411 case pageLog:
412 content, err = showRepoLogs(name, author)
413 case pageFiles:
414 content, err = showRepoFiles(name, author)
415 case pageRefs:
416 content, err = showRepoRefs(name, author)
417 case pageLicense:
418 content, err = showRepoLicense(name, author)
419 case pageReadme:
420 content, err = showRepoReadme(name, author)
421 }
422 if err != nil {
423 return c.NoContent(gig.StatusBadRequest,
424 "Invalid repository")
425 }
426
427 data := struct {
428 Protocol string
429 Domain string
430 User string
431 Description string
432 Repo string
433 Public bool
434 HasReadme bool
435 HasLicense bool
436 Content string
437 }{
438 Protocol: protocol,
439 Domain: config.Cfg.Git.Domain,
440 User: author,
441 Description: desc,
442 Repo: name,
443 Public: public,
444 HasReadme: hasFile(name, author, "README.gmi") ||
445 hasFile(name, author, "README"),
446 HasLicense: hasFile(name, author, "LICENSE"),
447 Content: content,
448 }
449 if owner {
450 return execT(c, "repo.gmi", data)
451 }
452 return execT(c, "public_repo.gmi", data)
453 }
454
455 func PublicList(c gig.Context) (error) {
456 repos, err := db.GetPublicRepo()
457 if err != nil {
458 log.Println(err.Error())
459 return c.NoContent(gig.StatusTemporaryFailure,
460 "Internal error, "+err.Error())
461 }
462 return execT(c, "public_list.gmi", repos)
463 }
464
465 func PublicAccount(c gig.Context) error {
466 user, err := db.GetPublicUser(c.Param("user"))
467 if err != nil {
468 return c.NoContent(gig.StatusBadRequest, err.Error())
469 }
470 repos, err := user.GetRepos(true)
471 if err != nil {
472 return c.NoContent(gig.StatusTemporaryFailure,
473 "Invalid account, " + err.Error())
474 }
475 data := struct {
476 Name string
477 Description string
478 Repositories []db.Repo
479 }{
480 user.Name,
481 user.Description,
482 repos,
483 }
484 return execT(c, "public_user.gmi", data)
485 }
486
487 func ShowAccess(c gig.Context) error {
488 user, exist := db.GetUser(c.CertHash())
489 if !exist {
490 return c.NoContent(gig.StatusBadRequest,
491 "Invalid username")
492 }
493 repo, err := user.GetRepo(c.Param("repo"))
494 if err != nil {
495 return c.NoContent(gig.StatusBadRequest, err.Error())
496 }
497 access, err := db.GetRepoAccess(repo.RepoID)
498 if err != nil {
499 return c.NoContent(gig.StatusBadRequest, err.Error())
500 }
501 groups, err := db.GetRepoGroupAccess(repo.RepoID)
502 if err != nil {
503 return c.NoContent(gig.StatusBadRequest, err.Error())
504 }
505 data := struct {
506 Repo string
507 Collaborators []db.Access
508 Groups []db.Access
509 Owner bool
510 }{
511 Repo: repo.Name,
512 Collaborators: access,
513 Groups: groups,
514 Owner: true,
515 }
516 return execT(c, "repo_access.gmi", data)
517 }
518