💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › ebc7a25c896f1e460b88029786… captured on 2023-12-28 at 15:34:45. 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 loggedAs := ""
418 user, exist := db.GetUser(c.CertHash())
419 if exist {
420 loggedAs = user.Name + "@"
421 }
422 author, name, err := getRepo(c, owner)
423 if err != nil {
424 log.Println(err.Error())
425 return c.NoContent(gig.StatusTemporaryFailure, err.Error())
426 }
427 desc, err := db.GetRepoDesc(name, author)
428 if err != nil {
429 log.Println(err.Error())
430 return c.NoContent(gig.StatusTemporaryFailure,
431 "Repository not found")
432 }
433 protocol := "http"
434 if config.Cfg.Git.Http.Https {
435 protocol = "https"
436 }
437 public, err := db.IsRepoPublic(name, author)
438 if err != nil {
439 log.Println(err.Error())
440 return c.NoContent(gig.StatusTemporaryFailure,
441 "Repository not found")
442 }
443 if public && config.Cfg.Git.Public {
444 loggedAs = "anon@"
445 }
446
447 content := ""
448 switch page {
449 case pageLog:
450 content, err = showRepoLogs(name, author)
451 case pageFiles:
452 content, err = showRepoFiles(name, author)
453 case pageRefs:
454 content, err = showRepoRefs(name, author)
455 case pageLicense:
456 content, err = showRepoLicense(name, author)
457 case pageReadme:
458 content, err = showRepoReadme(name, author)
459 }
460 if err != nil {
461 return c.NoContent(gig.StatusBadRequest,
462 "Invalid repository")
463 }
464
465 data := struct {
466 HasHTTP bool
467 HttpProtocol string
468 HttpDomain string
469 HasSSH bool
470 SshDomain string
471 LoggedAs string
472 User string
473 Description string
474 Repo string
475 Public bool
476 HasReadme bool
477 HasLicense bool
478 Content string
479 }{
480 HasHTTP: config.Cfg.Git.Http.Enabled,
481 HttpProtocol: protocol,
482 HttpDomain: config.Cfg.Git.Http.Domain,
483 HasSSH: config.Cfg.Git.SSH.Enabled,
484 SshDomain: config.Cfg.Git.SSH.Domain,
485 LoggedAs: loggedAs,
486 User: author,
487 Description: desc,
488 Repo: name,
489 Public: public,
490 HasReadme: hasFile(name, author, "README.gmi") ||
491 hasFile(name, author, "README.md") ||
492 hasFile(name, author, "README"),
493 HasLicense: hasFile(name, author, "LICENSE"),
494 Content: content,
495 }
496 if owner {
497 return execT(c, "repo.gmi", data)
498 }
499 return execT(c, "public_repo.gmi", data)
500 }
501
502 func PublicList(c gig.Context) (error) {
503 repos, err := db.GetPublicRepo()
504 if err != nil {
505 log.Println(err.Error())
506 return c.NoContent(gig.StatusTemporaryFailure,
507 "Internal error, "+err.Error())
508 }
509 return execT(c, "public_list.gmi", repos)
510 }
511
512 func PublicAccount(c gig.Context) error {
513 user, err := db.GetPublicUser(c.Param("user"))
514 if err != nil {
515 return c.NoContent(gig.StatusBadRequest, err.Error())
516 }
517 repos, err := user.GetRepos(true)
518 if err != nil {
519 return c.NoContent(gig.StatusTemporaryFailure,
520 "Invalid account, " + err.Error())
521 }
522 data := struct {
523 Name string
524 Description string
525 Repositories []db.Repo
526 }{
527 user.Name,
528 user.Description,
529 repos,
530 }
531 return execT(c, "public_user.gmi", data)
532 }
533
534 func ShowAccess(c gig.Context) error {
535 user, exist := db.GetUser(c.CertHash())
536 if !exist {
537 return c.NoContent(gig.StatusBadRequest, "Invalid username")
538 }
539 repo, err := user.GetRepo(c.Param("repo"))
540 if err != nil {
541 return c.NoContent(gig.StatusBadRequest, err.Error())
542 }
543 access, err := db.GetRepoUserAccess(repo.ID)
544 if err != nil {
545 return c.NoContent(gig.StatusBadRequest, err.Error())
546 }
547 groups, err := db.GetRepoGroupAccess(repo.ID)
548 if err != nil {
549 return c.NoContent(gig.StatusBadRequest, err.Error())
550 }
551 data := struct {
552 Repo string
553 Collaborators []db.Access
554 Groups []db.Access
555 Owner bool
556 }{
557 Repo: repo.Name,
558 Collaborators: access,
559 Groups: groups,
560 Owner: true,
561 }
562 return execT(c, "repo_access.gmi", data)
563 }
564