mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Initial git sorage backend implementation
This commit is contained in:
@@ -73,11 +73,6 @@ func DeleteFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
var defaultSettings = models.UserSettings{
|
||||
Theme: "light",
|
||||
AutoSave: false,
|
||||
}
|
||||
|
||||
func GetSettings(db *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userIDStr := r.URL.Query().Get("userId")
|
||||
@@ -93,7 +88,7 @@ func GetSettings(db *db.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
settings.SetDefaults(defaultSettings)
|
||||
settings.SetDefaults()
|
||||
|
||||
json.NewEncoder(w).Encode(settings)
|
||||
}
|
||||
@@ -107,7 +102,7 @@ func UpdateSettings(db *db.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
settings.SetDefaults(defaultSettings)
|
||||
settings.SetDefaults()
|
||||
|
||||
if err := settings.Validate(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@@ -132,3 +127,44 @@ func UpdateSettings(db *db.DB) http.HandlerFunc {
|
||||
json.NewEncoder(w).Encode(savedSettings)
|
||||
}
|
||||
}
|
||||
|
||||
func StageCommitAndPush(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var requestBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&requestBody)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if requestBody.Message == "" {
|
||||
http.Error(w, "Commit message is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = fs.StageCommitAndPush(requestBody.Message)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to stage, commit, and push changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Changes staged, committed, and pushed successfully"))
|
||||
}
|
||||
}
|
||||
|
||||
func PullChanges(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := fs.Pull()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to pull changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "Pulled changes from remote"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,9 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem) {
|
||||
r.Post("/*", SaveFile(fs))
|
||||
r.Delete("/*", DeleteFile(fs))
|
||||
})
|
||||
r.Route("/git", func(r chi.Router) {
|
||||
r.Post("commit", StageCommitAndPush(fs))
|
||||
r.Post("/pull", PullChanges(fs))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,13 +2,19 @@ package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"novamd/internal/gitutils"
|
||||
"novamd/internal/models"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
git "novamd/internal/gitutils"
|
||||
)
|
||||
|
||||
type FileSystem struct {
|
||||
RootDir string
|
||||
RootDir string
|
||||
GitRepo *gitutils.GitRepo
|
||||
Settings *models.Settings
|
||||
}
|
||||
|
||||
type FileNode struct {
|
||||
@@ -17,8 +23,30 @@ type FileNode struct {
|
||||
Files []FileNode `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
func New(rootDir string) *FileSystem {
|
||||
return &FileSystem{RootDir: rootDir}
|
||||
func New(rootDir string, settings *models.Settings) *FileSystem {
|
||||
fs := &FileSystem{
|
||||
RootDir: rootDir,
|
||||
Settings: settings,
|
||||
}
|
||||
|
||||
if settings.Settings.GitEnabled {
|
||||
fs.GitRepo = git.New(
|
||||
settings.Settings.GitURL,
|
||||
settings.Settings.GitUser,
|
||||
settings.Settings.GitToken,
|
||||
rootDir,
|
||||
)
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
func (fs *FileSystem) InitializeGitRepo() error {
|
||||
if fs.GitRepo == nil {
|
||||
return errors.New("git settings not configured")
|
||||
}
|
||||
|
||||
return fs.GitRepo.EnsureRepo()
|
||||
}
|
||||
|
||||
// validatePath checks if the given path is within the root directory
|
||||
@@ -96,6 +124,11 @@ func (fs *FileSystem) SaveFile(filePath string, content []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if fs.Settings.Settings.GitAutoCommit && fs.GitRepo != nil {
|
||||
message := strings.Replace(fs.Settings.Settings.GitCommitMsgTemplate, "${filename}", filePath, -1)
|
||||
return fs.StageCommitAndPush(message)
|
||||
}
|
||||
|
||||
return os.WriteFile(fullPath, content, 0644)
|
||||
}
|
||||
|
||||
@@ -106,3 +139,23 @@ func (fs *FileSystem) DeleteFile(filePath string) error {
|
||||
}
|
||||
return os.Remove(fullPath)
|
||||
}
|
||||
|
||||
func (fs *FileSystem) StageCommitAndPush(message string) error {
|
||||
if fs.GitRepo == nil {
|
||||
return errors.New("git settings not configured")
|
||||
}
|
||||
|
||||
if err := fs.GitRepo.Commit(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fs.GitRepo.Push()
|
||||
}
|
||||
|
||||
func (fs *FileSystem) Pull() error {
|
||||
if fs.GitRepo == nil {
|
||||
return errors.New("git settings not configured")
|
||||
}
|
||||
|
||||
return fs.GitRepo.Pull()
|
||||
}
|
||||
|
||||
133
backend/internal/gitutils/git.go
Normal file
133
backend/internal/gitutils/git.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package gitutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
)
|
||||
|
||||
type GitRepo struct {
|
||||
URL string
|
||||
Username string
|
||||
Token string
|
||||
WorkDir string
|
||||
repo *git.Repository
|
||||
}
|
||||
|
||||
func New(url, username, token, workDir string) *GitRepo {
|
||||
return &GitRepo{
|
||||
URL: url,
|
||||
Username: username,
|
||||
Token: token,
|
||||
WorkDir: workDir,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitRepo) Clone() error {
|
||||
auth := &http.BasicAuth{
|
||||
Username: g.Username,
|
||||
Password: g.Token,
|
||||
}
|
||||
|
||||
var err error
|
||||
g.repo, err = git.PlainClone(g.WorkDir, false, &git.CloneOptions{
|
||||
URL: g.URL,
|
||||
Auth: auth,
|
||||
Progress: os.Stdout,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) Pull() error {
|
||||
if g.repo == nil {
|
||||
return fmt.Errorf("repository not initialized")
|
||||
}
|
||||
|
||||
w, err := g.repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
auth := &http.BasicAuth{
|
||||
Username: g.Username,
|
||||
Password: g.Token,
|
||||
}
|
||||
|
||||
err = w.Pull(&git.PullOptions{
|
||||
Auth: auth,
|
||||
Progress: os.Stdout,
|
||||
})
|
||||
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return fmt.Errorf("failed to pull changes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) Commit(message string) error {
|
||||
if g.repo == nil {
|
||||
return fmt.Errorf("repository not initialized")
|
||||
}
|
||||
|
||||
w, err := g.repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Add(".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add changes: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Commit(message, &git.CommitOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) Push() error {
|
||||
if g.repo == nil {
|
||||
return fmt.Errorf("repository not initialized")
|
||||
}
|
||||
|
||||
auth := &http.BasicAuth{
|
||||
Username: g.Username,
|
||||
Password: g.Token,
|
||||
}
|
||||
|
||||
err := g.repo.Push(&git.PushOptions{
|
||||
Auth: auth,
|
||||
Progress: os.Stdout,
|
||||
})
|
||||
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return fmt.Errorf("failed to push changes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) EnsureRepo() error {
|
||||
if _, err := os.Stat(filepath.Join(g.WorkDir, ".git")); os.IsNotExist(err) {
|
||||
return g.Clone()
|
||||
}
|
||||
|
||||
var err error
|
||||
g.repo, err = git.PlainOpen(g.WorkDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open existing repository: %w", err)
|
||||
}
|
||||
|
||||
return g.Pull()
|
||||
}
|
||||
@@ -7,8 +7,14 @@ import (
|
||||
)
|
||||
|
||||
type UserSettings struct {
|
||||
Theme string `json:"theme" validate:"oneof=light dark"`
|
||||
AutoSave bool `json:"autoSave"`
|
||||
Theme string `json:"theme" validate:"oneof=light dark"`
|
||||
AutoSave bool `json:"autoSave"`
|
||||
GitEnabled bool `json:"git_enabled"`
|
||||
GitURL string `json:"git_url" validate:"required_with=GitEnabled"`
|
||||
GitUser string `json:"git_user" validate:"required_with=GitEnabled"`
|
||||
GitToken string `json:"git_token" validate:"required_with=GitEnabled"`
|
||||
GitAutoCommit bool `json:"git_auto_commit"`
|
||||
GitCommitMsgTemplate string `json:"git_commit_msg_template"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
@@ -16,15 +22,25 @@ type Settings struct {
|
||||
Settings UserSettings `json:"settings" validate:"required"`
|
||||
}
|
||||
|
||||
var defaultUserSettings = UserSettings{
|
||||
Theme: "light",
|
||||
AutoSave: false,
|
||||
GitEnabled: false,
|
||||
GitCommitMsgTemplate: "Update ${filename}",
|
||||
}
|
||||
|
||||
var validate = validator.New()
|
||||
|
||||
func (s *Settings) Validate() error {
|
||||
return validate.Struct(s)
|
||||
}
|
||||
|
||||
func (s *Settings) SetDefaults(defaults UserSettings) {
|
||||
func (s *Settings) SetDefaults() {
|
||||
if s.Settings.Theme == "" {
|
||||
s.Settings.Theme = defaults.Theme
|
||||
s.Settings.Theme = defaultUserSettings.Theme
|
||||
}
|
||||
if s.Settings.GitCommitMsgTemplate == "" {
|
||||
s.Settings.GitCommitMsgTemplate = defaultUserSettings.GitCommitMsgTemplate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user