💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › 8c8a7c29a2e49afb6bdeb504de… captured on 2023-09-08 at 16:30:38. 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 err = ret.ForEach(func(c *object.Commit) error {
305 info := c.Hash.String() + ", by " + c.Author.Name + " on " +
306 c.Author.When.Format("2006-01-02 15:04:05")
307 commits = append(commits, commit{Info: info,
308 Message: c.Message})
309 return nil
310 })
311 return execTemplate("repo_log.gmi", commits)
312 }
313
314 func showRepoFiles(name string, author string) (string, error) {
315 ret, err := repo.GetFiles(name, author)
316 if ret == nil || err == transport.ErrEmptyRemoteRepository {
317 return "", nil
318 }
319 if err != nil {
320 log.Println(err.Error())
321 return "", errors.New("Corrupted repository")
322 }
323 files := []file{}
324 err = ret.ForEach(func(f *object.File) error {
325 info := f.Mode.String() + " " + f.Name +
326 " " + strconv.Itoa(int(f.Size))
327 files = append(files, file{Info: info,
328 Hash: f.Blob.Hash.String()})
329 return nil
330 })
331 return execTemplate("repo_files.gmi", files)
332 }
333
334 func showRepoRefs(name string, author string) (string, error) {
335 refs, err := repo.GetRefs(name, author)
336 if refs == nil || err == transport.ErrEmptyRemoteRepository {
337 return "", nil
338 }
339 if err != nil {
340 log.Println(err)
341 return "", errors.New("Corrupted repository")
342 }
343 branches := []branch{}
344 tags := []branch{}
345 err = refs.ForEach(func(c *plumbing.Reference) error {
346 if c.Type().String() != "hash-reference" ||
347 c.Name().IsRemote() {
348 return nil
349 }
350 var b branch
351 b.Name = c.Name().String()
352 b.Name = b.Name[strings.LastIndex(b.Name, "/") + 1:]
353 b.Info = "last commit on "
354
355 commit, err := repo.GetCommit(name, author, c.Hash())
356 if err != nil {
357 b.Info = "failed to fetch commit"
358 } else {
359 when := commit.Author.When
360 str := fmt.Sprintf(
361 "%d-%02d-%02d %02d:%02d:%02d",
362 when.Year(), int(when.Month()),
363 when.Day(), when.Hour(),
364 when.Minute(), when.Second())
365 b.Info += str + " by " + commit.Author.Name
366 }
367 if c.Name().IsBranch() {
368 branches = append(branches, b)
369 } else {
370 tags = append(tags, b)
371 }
372 return nil
373 })
374 refs.Close()
375 data := struct {
376 Branches []branch
377 Tags []branch
378 }{
379 branches,
380 tags,
381 }
382 return execTemplate("repo_refs.gmi", data)
383 }
384
385 func showRepoLicense(name string, author string) (string, error) {
386 content, err := showRepoFile(author, name, "LICENSE")
387 if err != nil {
388 return "", errors.New("No license found")
389 }
390 return execTemplate("repo_license.gmi", content)
391 }
392
393 func showRepoReadme(name string, author string) (string, error) {
394 content, err := showRepoFile(author, name, "README.gmi")
395 if err != nil {
396 content, err = showRepoFile(author, name, "README")
397 }
398 if err != nil {
399 content, err = showRepoFile(author, name, "README.md")
400 if err == nil {
401 content = fromMarkdownToGmi(content)
402 }
403 }
404 if err != nil {
405 return "", errors.New("No readme found")
406 }
407 return execTemplate("repo_readme.gmi", content)
408 }
409
410 func showRepo(c gig.Context, page int, owner bool) (error) {
411 author, name, err := getRepo(c, owner)
412 if err != nil {
413 log.Println(err.Error())
414 return c.NoContent(gig.StatusTemporaryFailure, err.Error())
415 }
416 desc, err := db.GetRepoDesc(name, author)
417 if err != nil {
418 log.Println(err.Error())
419 return c.NoContent(gig.StatusTemporaryFailure,
420 "Repository not found")
421 }
422 protocol := "http"
423 if config.Cfg.Git.Https {
424 protocol = "https"
425 }
426 public, err := db.IsRepoPublic(name, author)
427 if err != nil {
428 log.Println(err.Error())
429 return c.NoContent(gig.StatusTemporaryFailure,
430 "Repository not found")
431 }
432
433 content := ""
434 switch page {
435 case pageLog:
436 content, err = showRepoLogs(name, author)
437 case pageFiles:
438 content, err = showRepoFiles(name, author)
439 case pageRefs:
440 content, err = showRepoRefs(name, author)
441 case pageLicense:
442 content, err = showRepoLicense(name, author)
443 case pageReadme:
444 content, err = showRepoReadme(name, author)
445 }
446 if err != nil {
447 return c.NoContent(gig.StatusBadRequest,
448 "Invalid repository")
449 }
450
451 data := struct {
452 Protocol string
453 Domain string
454 User string
455 Description string
456 Repo string
457 Public bool
458 HasReadme bool
459 HasLicense bool
460 Content string
461 }{
462 Protocol: protocol,
463 Domain: config.Cfg.Git.Domain,
464 User: author,
465 Description: desc,
466 Repo: name,
467 Public: public,
468 HasReadme: hasFile(name, author, "README.gmi") ||
469 hasFile(name, author, "README.md") ||
470 hasFile(name, author, "README"),
471 HasLicense: hasFile(name, author, "LICENSE"),
472 Content: content,
473 }
474 if owner {
475 return execT(c, "repo.gmi", data)
476 }
477 return execT(c, "public_repo.gmi", data)
478 }
479
480 func PublicList(c gig.Context) (error) {
481 repos, err := db.GetPublicRepo()
482 if err != nil {
483 log.Println(err.Error())
484 return c.NoContent(gig.StatusTemporaryFailure,
485 "Internal error, "+err.Error())
486 }
487 return execT(c, "public_list.gmi", repos)
488 }
489
490 func PublicAccount(c gig.Context) error {
491 user, err := db.GetPublicUser(c.Param("user"))
492 if err != nil {
493 return c.NoContent(gig.StatusBadRequest, err.Error())
494 }
495 repos, err := user.GetRepos(true)
496 if err != nil {
497 return c.NoContent(gig.StatusTemporaryFailure,
498 "Invalid account, " + err.Error())
499 }
500 data := struct {
501 Name string
502 Description string
503 Repositories []db.Repo
504 }{
505 user.Name,
506 user.Description,
507 repos,
508 }
509 return execT(c, "public_user.gmi", data)
510 }
511
512 func ShowAccess(c gig.Context) error {
513 user, exist := db.GetUser(c.CertHash())
514 if !exist {
515 return c.NoContent(gig.StatusBadRequest, "Invalid username")
516 }
517 repo, err := user.GetRepo(c.Param("repo"))
518 if err != nil {
519 return c.NoContent(gig.StatusBadRequest, err.Error())
520 }
521 access, err := db.GetRepoUserAccess(repo.ID)
522 if err != nil {
523 return c.NoContent(gig.StatusBadRequest, err.Error())
524 }
525 groups, err := db.GetRepoGroupAccess(repo.ID)
526 if err != nil {
527 return c.NoContent(gig.StatusBadRequest, err.Error())
528 }
529 data := struct {
530 Repo string
531 Collaborators []db.Access
532 Groups []db.Access
533 Owner bool
534 }{
535 Repo: repo.Name,
536 Collaborators: access,
537 Groups: groups,
538 Owner: true,
539 }
540 return execT(c, "repo_access.gmi", data)
541 }
542