Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Let instance admins create repositories

455835e7b5d329ca64c96234dfffc7b6702f370e · Keenan Nemetz · 15d ago · parent 395554f4

Let instance admins create repositories

The Admin section gains a New repository form: name, owner, and description. Creation uses the same store method as the CLI, seeds the owner as repo admin, updates the index, is audited, and lands on the new repository's access editor. This completes the instance-admin slice in the UI plan.

server/web/admin_repos.go +83

@@ -0,0 +1,83 @@
1 package web
2
3 // Creating a repository is an instance-admin operation because it chooses the
4 // owner and the name that every clone URL will use. After creation the admin is
5 // sent straight to the access editor, which is the next thing they usually need.
6
7 import (
8 "net/http"
9 "strings"
10
11 "github.com/rodent-software/vole/forge/identity"
12 )
13
14 type adminRepoNewView struct {
15 Page
16 Accounts []*identity.Account
17 Name string
18 Description string
19 Owner string
20 Error string
21 }
22
23 func (w *Web) adminRepoNewView(r *http.Request, msg string) adminRepoNewView {
24 var accounts []*identity.Account
25 for _, acct := range w.Ident.All() {
26 if acct.Active() {
27 accounts = append(accounts, acct)
28 }
29 }
30 return adminRepoNewView{Page: w.page(r, "New repository"), Accounts: accounts, Error: msg}
31 }
32
33 func (w *Web) AdminNewRepoPage(rw http.ResponseWriter, r *http.Request) {
34 if _, ok := w.requireInstanceAdmin(rw, r); !ok {
35 return
36 }
37 w.render(rw, "admin_repo_new", w.adminRepoNewView(r, ""), 0)
38 }
39
40 func (w *Web) CreateAdminRepo(rw http.ResponseWriter, r *http.Request) {
41 pr, ok := w.requireInstanceAdmin(rw, r)
42 if !ok {
43 return
44 }
45 r.Body = http.MaxBytesReader(rw, r.Body, 1<<20)
46 if !w.checkCSRF(r) {
47 http.Error(rw, "bad request", http.StatusForbidden)
48 return
49 }
50 if err := r.ParseForm(); err != nil {
51 http.Error(rw, "cannot read the form", http.StatusBadRequest)
52 return
53 }
54 name := strings.TrimSpace(r.PostFormValue("name"))
55 ownerName := strings.TrimSpace(r.PostFormValue("owner"))
56 description := strings.TrimSpace(r.PostFormValue("description"))
57
58 view := w.adminRepoNewView(r, "")
59 view.Name, view.Owner, view.Description = name, ownerName, description
60 fail := func(msg string) {
61 view.Error = msg
62 w.render(rw, "admin_repo_new", view, http.StatusBadRequest)
63 }
64
65 owner, err := w.Ident.Find(ownerName)
66 if err != nil || !owner.Active() {
67 fail("Choose an active account as the owner.")
68 return
69 }
70 m, err := w.Repos.Create(name, owner.ID, description)
71 if err != nil {
72 fail(err.Error())
73 return
74 }
75 if w.Index != nil {
76 if err := w.Index.Upsert(m); err != nil {
77 w.serverError(rw, r, "repository created but the index was not updated: "+err.Error())
78 return
79 }
80 }
81 w.audit("web.admin.repo.create", pr.account.ID, m.Name+" owner="+owner.Name)
82 http.Redirect(rw, r, "/admin/repos/"+m.Name+"/access", http.StatusSeeOther)
83 }