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:
2026-05-22 19:53:24 +02:00
parent 0c1047d390
commit 4df87cbf9a
8 changed files with 830 additions and 3 deletions
+161
View File
@@ -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"})
}