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:
2026-05-07 16:12:25 +02:00
parent 39eeccb314
commit 803672a610
7 changed files with 328 additions and 2 deletions
+132
View File
@@ -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,
}
}