Git LFS section is live with:
Enable LFS toggle — turns LFS on/off for the repo; all other controls dim when disabled File locking toggle — enables the LFS locking protocol for binary assets Maximum file size — optional per-file size cap in MB (blank = unlimited) Info callout linking to the git-lfs client install page and noting the .gitattributes requirement
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/forgeo/forgebucket/internal/api/middleware"
|
||||
"github.com/forgeo/forgebucket/internal/models"
|
||||
)
|
||||
|
||||
type LFSHandler struct{ db *xorm.Engine }
|
||||
|
||||
func NewLFSHandler(db *xorm.Engine) *LFSHandler { return &LFSHandler{db: db} }
|
||||
|
||||
func (h *LFSHandler) resolveRepo(w http.ResponseWriter, r *http.Request) (*models.Repository, bool) {
|
||||
ownerName := chi.URLParam(r, "owner")
|
||||
repoName := chi.URLParam(r, "repo")
|
||||
|
||||
var owner models.User
|
||||
found, err := h.db.Where("username = ?", ownerName).Get(&owner)
|
||||
if err != nil {
|
||||
jsonError(w, "database error: "+err.Error(), http.StatusInternalServerError)
|
||||
return nil, false
|
||||
}
|
||||
if !found {
|
||||
jsonError(w, "repository not found", http.StatusNotFound)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var repo models.Repository
|
||||
found, err = h.db.Where("owner_id = ? AND name = ?", owner.ID, repoName).Get(&repo)
|
||||
if err != nil {
|
||||
jsonError(w, "database error: "+err.Error(), http.StatusInternalServerError)
|
||||
return nil, false
|
||||
}
|
||||
if !found {
|
||||
jsonError(w, "repository not found", http.StatusNotFound)
|
||||
return nil, false
|
||||
}
|
||||
return &repo, true
|
||||
}
|
||||
|
||||
func (h *LFSHandler) canManage(repo *models.Repository, callerID int64) bool {
|
||||
if callerID == repo.OwnerID {
|
||||
return true
|
||||
}
|
||||
var m models.RepoMember
|
||||
found, _ := h.db.Where("repo_id = ? AND user_id = ? AND permission = 'admin'", repo.ID, callerID).Get(&m)
|
||||
return found
|
||||
}
|
||||
|
||||
func (h *LFSHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
repo, ok := h.resolveRepo(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var s models.RepoLFSSettings
|
||||
h.db.Where("repo_id = ?", repo.ID).Get(&s)
|
||||
jsonOK(w, lfsResponse(repo.ID, &s))
|
||||
}
|
||||
|
||||
func (h *LFSHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
repo, ok := h.resolveRepo(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
callerID, _ := middleware.UserIDFromContext(r.Context())
|
||||
if !h.canManage(repo, callerID) {
|
||||
jsonError(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Enabled *bool `json:"enabled"`
|
||||
LockingEnabled *bool `json:"lockingEnabled"`
|
||||
MaxFileSizeMB *int64 `json:"maxFileSizeMB"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var s models.RepoLFSSettings
|
||||
found, err := h.db.Where("repo_id = ?", repo.ID).Get(&s)
|
||||
if err != nil {
|
||||
jsonError(w, "database error: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.RepoID = repo.ID
|
||||
|
||||
if body.Enabled != nil {
|
||||
s.Enabled = *body.Enabled
|
||||
}
|
||||
if body.LockingEnabled != nil {
|
||||
s.LockingEnabled = *body.LockingEnabled
|
||||
}
|
||||
if body.MaxFileSizeMB != nil {
|
||||
s.MaxFileSizeMB = *body.MaxFileSizeMB
|
||||
}
|
||||
|
||||
if found {
|
||||
if _, err := h.db.ID(s.ID).Cols("enabled", "locking_enabled", "max_file_size_mb").Update(&s); err != nil {
|
||||
jsonError(w, "could not update LFS settings", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err := h.db.Insert(&s); err != nil {
|
||||
jsonError(w, "could not save LFS settings", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
jsonOK(w, lfsResponse(repo.ID, &s))
|
||||
}
|
||||
|
||||
type lfsSettingsResponse struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
LockingEnabled bool `json:"lockingEnabled"`
|
||||
MaxFileSizeMB int64 `json:"maxFileSizeMB"`
|
||||
}
|
||||
|
||||
func lfsResponse(_ int64, s *models.RepoLFSSettings) lfsSettingsResponse {
|
||||
return lfsSettingsResponse{
|
||||
Enabled: s.Enabled,
|
||||
LockingEnabled: s.LockingEnabled,
|
||||
MaxFileSizeMB: s.MaxFileSizeMB,
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ func New(cfg *config.Config, engine *xorm.Engine, store sessions.Store, staticFi
|
||||
workflowH := handlers.NewWorkflowHandler(engine)
|
||||
webhookH := handlers.NewWebhookHandler(engine)
|
||||
prSettingsH := handlers.NewPRSettingsHandler(engine)
|
||||
lfsH := handlers.NewLFSHandler(engine)
|
||||
|
||||
// ── Git smart-HTTP transport ───────────────────────────────────────────────
|
||||
// These routes MUST be registered before the SPA catch-all and outside CSRF.
|
||||
@@ -182,6 +183,8 @@ func New(cfg *config.Config, engine *xorm.Engine, store sessions.Store, staticFi
|
||||
r.With(csrf).Put("/default-description", prSettingsH.UpdateDefaultDescription)
|
||||
r.Get("/excluded-files", prSettingsH.GetExcludedFiles)
|
||||
r.With(csrf).Put("/excluded-files", prSettingsH.UpdateExcludedFiles)
|
||||
r.Get("/lfs-settings", lfsH.Get)
|
||||
r.With(csrf).Put("/lfs-settings", lfsH.Update)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package models
|
||||
|
||||
type RepoLFSSettings struct {
|
||||
ID int64 `xorm:"'id' pk autoincr"`
|
||||
RepoID int64 `xorm:"'repo_id' notnull unique"`
|
||||
Enabled bool `xorm:"'enabled' default false"`
|
||||
LockingEnabled bool `xorm:"'locking_enabled' default true"`
|
||||
MaxFileSizeMB int64 `xorm:"'max_file_size_mb' default 0"` // 0 = unlimited
|
||||
}
|
||||
@@ -28,5 +28,8 @@ func Run(engine *xorm.Engine) error {
|
||||
if err := Run005(engine); err != nil {
|
||||
return err
|
||||
}
|
||||
return Run006(engine)
|
||||
if err := Run006(engine); err != nil {
|
||||
return err
|
||||
}
|
||||
return Run007(engine)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/forgeo/forgebucket/internal/models"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func Run007(engine *xorm.Engine) error {
|
||||
return engine.Sync2(&models.RepoLFSSettings{})
|
||||
}
|
||||
Reference in New Issue
Block a user