Phase 2-6: Git sync, sharing, 2FA, AI integration
- Git: init, commit, log, diff, restore, remotes, push/pull - Auto-commit on every file save - Sharing: share/unshare files with other users (ro/rw) - Shared documents view in sidebar - 2FA: TOTP setup/verify/disable, enforced at login - AI: verify spec endpoint (LiteLLM), generate (summarize/prompt/expand) - Light/dark theme with CSS variables - File delete (recursive for folders) - Admin panel + preferences panel - File creation timestamp display
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"markdownhub/internal/git"
|
||||
)
|
||||
|
||||
func (s *Server) handleGitInit(w http.ResponseWriter, r *http.Request) {
|
||||
userID := getUserID(r)
|
||||
if err := git.InitRepo(s.dataDir, userID); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "git init failed"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitCommit(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
decodeBody(r, &req)
|
||||
userID := getUserID(r)
|
||||
if req.Message == "" {
|
||||
req.Message = "Manual save"
|
||||
}
|
||||
if err := git.Commit(s.dataDir, userID, req.Message); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "commit failed"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitLog(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
decodeBody(r, &req)
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 30
|
||||
}
|
||||
userID := getUserID(r)
|
||||
commits, err := git.Log(s.dataDir, userID, req.Path, req.Limit)
|
||||
if err != nil {
|
||||
writeJSON(w, 200, []git.CommitInfo{})
|
||||
return
|
||||
}
|
||||
if commits == nil {
|
||||
commits = []git.CommitInfo{}
|
||||
}
|
||||
writeJSON(w, 200, commits)
|
||||
}
|
||||
|
||||
func (s *Server) handleGitDiff(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
if err := decodeBody(r, &req); err != nil || req.Hash == "" {
|
||||
writeJSON(w, 400, map[string]string{"error": "hash required"})
|
||||
return
|
||||
}
|
||||
userID := getUserID(r)
|
||||
diff, err := git.Diff(s.dataDir, userID, req.Hash)
|
||||
if err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "diff failed"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"diff": diff})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitRestore(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
if err := decodeBody(r, &req); err != nil || req.Path == "" || req.Hash == "" {
|
||||
writeJSON(w, 400, map[string]string{"error": "path and hash required"})
|
||||
return
|
||||
}
|
||||
userID := getUserID(r)
|
||||
if err := git.Restore(s.dataDir, userID, req.Path, req.Hash); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "restore failed"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitStatus(w http.ResponseWriter, r *http.Request) {
|
||||
userID := getUserID(r)
|
||||
dirty, err := git.Status(s.dataDir, userID)
|
||||
if err != nil {
|
||||
writeJSON(w, 200, map[string]interface{}{"dirty": 0})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]interface{}{"dirty": dirty})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitRemoteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := decodeBody(r, &req); err != nil || req.Name == "" || req.URL == "" {
|
||||
writeJSON(w, 400, map[string]string{"error": "name and url required"})
|
||||
return
|
||||
}
|
||||
userID := getUserID(r)
|
||||
if err := git.AddRemote(s.dataDir, userID, req.Name, req.URL); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "add remote failed"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitRemoteList(w http.ResponseWriter, r *http.Request) {
|
||||
userID := getUserID(r)
|
||||
remotes, err := git.ListRemotes(s.dataDir, userID)
|
||||
if err != nil {
|
||||
writeJSON(w, 200, []git.RemoteInfo{})
|
||||
return
|
||||
}
|
||||
if remotes == nil {
|
||||
remotes = []git.RemoteInfo{}
|
||||
}
|
||||
writeJSON(w, 200, remotes)
|
||||
}
|
||||
|
||||
func (s *Server) handleGitPush(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Remote string `json:"remote"`
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
decodeBody(r, &req)
|
||||
if req.Remote == "" {
|
||||
req.Remote = "origin"
|
||||
}
|
||||
userID := getUserID(r)
|
||||
if err := git.Push(s.dataDir, userID, req.Remote, req.Branch); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "push failed: " + err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) handleGitPull(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Remote string `json:"remote"`
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
decodeBody(r, &req)
|
||||
if req.Remote == "" {
|
||||
req.Remote = "origin"
|
||||
}
|
||||
userID := getUserID(r)
|
||||
if err := git.Pull(s.dataDir, userID, req.Remote, req.Branch); err != nil {
|
||||
writeJSON(w, 500, map[string]string{"error": "pull failed: " + err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]string{"status": "ok"})
|
||||
}
|
||||
Reference in New Issue
Block a user