aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-03-06 22:01:08 +0800
committerRunxi Yu <me@runxiyu.org>2025-03-06 22:01:31 +0800
commitfa62d8eae273d89937d65d6a294f028e1ce22d88 (patch)
tree226d5fab65927ef977b664a35e6a2b2f30169440
parentgroup: Add description field to the create repo form (diff)
downloadforge-fa62d8eae273d89937d65d6a294f028e1ce22d88.tar.gz
forge-fa62d8eae273d89937d65d6a294f028e1ce22d88.tar.zst
forge-fa62d8eae273d89937d65d6a294f028e1ce22d88.zip
group/index: Allow repo creation via web
-rw-r--r--config.go3
-rw-r--r--forge.scfg4
-rw-r--r--http_handle_group_index.go56
-rw-r--r--static/style.css16
-rw-r--r--templates/group.tmpl11
-rw-r--r--url.go15
6 files changed, 105 insertions, 0 deletions
diff --git a/config.go b/config.go
index cba35d5..8ed0d01 100644
--- a/config.go
+++ b/config.go
@@ -28,6 +28,9 @@ var config struct {
Socket string `scfg:"socket"`
Execs string `scfg:"execs"`
} `scfg:"hooks"`
+ Git struct {
+ RepoDir string `scfg:"repo_dir"`
+ } `scfg:"git"`
SSH struct {
Net string `scfg:"net"`
Addr string `scfg:"addr"`
diff --git a/forge.scfg b/forge.scfg
index 3b8aafb..cf77a4b 100644
--- a/forge.scfg
+++ b/forge.scfg
@@ -15,6 +15,10 @@ http {
root https://forge.example.org
}
+git {
+ repo_dir /var/lib/lindenii/forge/repos
+}
+
ssh {
# What network transport should we listen on?
# This should be "tcp" in almost all cases.
diff --git a/http_handle_group_index.go b/http_handle_group_index.go
index 97ffee5..edb1368 100644
--- a/http_handle_group_index.go
+++ b/http_handle_group_index.go
@@ -5,6 +5,8 @@ package main
import (
"net/http"
+ "path/filepath"
+ "strconv"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
@@ -74,6 +76,60 @@ func handle_group_index(w http.ResponseWriter, r *http.Request, params map[strin
}
direct_access := (count > 0)
+ if r.Method == "POST" {
+ if !direct_access {
+ http.Error(w, "You do not have direct access to this group", http.StatusForbidden)
+ return
+ }
+
+ repo_name := r.FormValue("repo_name")
+ repo_description := r.FormValue("repo_description")
+ contrib_requirements := r.FormValue("repo_contrib")
+ if repo_name == "" {
+ http.Error(w, "Repo name is required", http.StatusBadRequest)
+ return
+ }
+
+ var new_repo_id int
+ err := database.QueryRow(
+ r.Context(),
+ `INSERT INTO repos (name, description, group_id, contrib_requirements)
+ VALUES ($1, $2, $3, $4)
+ RETURNING id`,
+ repo_name,
+ repo_description,
+ group_id,
+ contrib_requirements,
+ ).Scan(&new_repo_id)
+ if err != nil {
+ http.Error(w, "Error creating repo: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ file_path := filepath.Join(config.Git.RepoDir, strconv.Itoa(new_repo_id)+".git")
+
+ _, err = database.Exec(
+ r.Context(),
+ `UPDATE repos
+ SET filesystem_path = $1
+ WHERE id = $2`,
+ file_path,
+ new_repo_id,
+ )
+ if err != nil {
+ http.Error(w, "Error updating repo path: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if err = git_bare_init_with_default_hooks(file_path); err != nil {
+ http.Error(w, "Error initializing repo: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ redirect_unconditionally(w, r)
+ return
+ }
+
// Repos
var rows pgx.Rows
rows, err = database.Query(r.Context(), `
diff --git a/static/style.css b/static/style.css
index 5e35cd1..0f53c86 100644
--- a/static/style.css
+++ b/static/style.css
@@ -271,6 +271,7 @@ input[type=password] {
}
td.tdinput, th.tdinput {
padding: 0;
+ position: relative;
}
td.tdinput textarea,
td.tdinput input[type=text],
@@ -280,6 +281,21 @@ th.tdinput input[type=text],
th.tdinput input[type=password] {
background-color: transparent;
}
+td.tdinput select {
+ position: absolute;
+ background-color: var(--background-color);
+ border: none;
+ /*
+ width: 100%;
+ height: 100%;
+ */
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
/* Button styles */
.btn-primary, a.btn-primary {
diff --git a/templates/group.tmpl b/templates/group.tmpl
index ae0892c..5f3f0da 100644
--- a/templates/group.tmpl
+++ b/templates/group.tmpl
@@ -44,6 +44,17 @@
<input id="repo-desc-input" name="repo_desc" type="text" />
</td>
</tr>
+ <tr>
+ <th scope="row">Contrib</th>
+ <td class="tdinput">
+ <select id="repo-contrib-input" name="repo_contrib">
+ <option value="public">Public</option>
+ <option value="registered_user">Registered user</option>
+ <option value="ssh_pubkey">SSH public key</option>
+ <option value="closed">Closed</option>
+ </select>
+ </td>
+ </tr>
</tbody>
<tfoot>
<tr>
diff --git a/url.go b/url.go
index 8be7047..393913f 100644
--- a/url.go
+++ b/url.go
@@ -99,6 +99,21 @@ func redirect_without_slash(w http.ResponseWriter, r *http.Request) bool {
return false
}
+func redirect_unconditionally(w http.ResponseWriter, r *http.Request) {
+ request_uri := r.RequestURI
+
+ path_end := strings.IndexAny(request_uri, "?#")
+ var path, rest string
+ if path_end == -1 {
+ path = request_uri
+ } else {
+ path = request_uri[:path_end]
+ rest = request_uri[path_end:]
+ }
+
+ http.Redirect(w, r, path+rest, http.StatusSeeOther)
+}
+
func path_escape_cat_segments(segments []string) string {
for i, segment := range segments {
segments[i] = url.PathEscape(segment)