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