mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Rework request context handler
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"novamd/internal/config"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -74,7 +75,7 @@ func main() {
|
||||
})
|
||||
|
||||
// Handle all other routes with static file server
|
||||
r.Get("/*", api.NewStaticHandler(cfg.StaticPath).ServeHTTP)
|
||||
r.Get("/*", handlers.NewStaticHandler(cfg.StaticPath).ServeHTTP)
|
||||
|
||||
// Start server
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// api/context.go
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/models"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type HandlerContext struct {
|
||||
UserID int
|
||||
UserRole string
|
||||
Workspace *models.Workspace
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
const handlerContextKey contextKey = "handlerContext"
|
||||
|
||||
// Middleware to populate handler context
|
||||
func WithHandlerContext(db *db.DB) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get user claims from auth context
|
||||
claims, err := auth.GetUserFromContext(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Get workspace name from URL if it exists
|
||||
workspaceName := chi.URLParam(r, "workspaceName")
|
||||
|
||||
var workspace *models.Workspace
|
||||
// Only look up workspace if name is provided
|
||||
if workspaceName != "" {
|
||||
workspace, err = db.GetWorkspaceByName(claims.UserID, workspaceName)
|
||||
if err != nil {
|
||||
http.Error(w, "Workspace not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create handler context
|
||||
ctx := &HandlerContext{
|
||||
UserID: claims.UserID,
|
||||
UserRole: claims.Role,
|
||||
Workspace: workspace,
|
||||
}
|
||||
|
||||
// Add to request context
|
||||
reqCtx := context.WithValue(r.Context(), handlerContextKey, ctx)
|
||||
next.ServeHTTP(w, r.WithContext(reqCtx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get handler context
|
||||
func GetHandlerContext(r *http.Request) *HandlerContext {
|
||||
ctx := r.Context().Value(handlerContextKey)
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
return ctx.(*HandlerContext)
|
||||
}
|
||||
|
||||
type BaseHandler struct {
|
||||
DB *db.DB
|
||||
FS *filesystem.FileSystem
|
||||
}
|
||||
|
||||
// Helper method to get context and handle errors
|
||||
func (h *BaseHandler) getContext(w http.ResponseWriter, r *http.Request) (*HandlerContext, bool) {
|
||||
ctx := GetHandlerContext(r)
|
||||
if ctx == nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return nil, false
|
||||
}
|
||||
return ctx, true
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func getWorkspaceID(r *http.Request) (int, error) {
|
||||
workspaceIDStr := chi.URLParam(r, "workspaceId")
|
||||
workspaceID, err := strconv.Atoi(workspaceIDStr)
|
||||
if err != nil {
|
||||
return 0, errors.New("invalid workspaceId")
|
||||
}
|
||||
|
||||
return workspaceID, nil
|
||||
}
|
||||
|
||||
func respondJSON(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -4,32 +4,34 @@ import (
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/handlers"
|
||||
"novamd/internal/middleware"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddleware *auth.Middleware, sessionService *auth.SessionService) {
|
||||
|
||||
handler := &BaseHandler{
|
||||
handler := &handlers.Handler{
|
||||
DB: db,
|
||||
FS: fs,
|
||||
}
|
||||
|
||||
// Public routes (no authentication required)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post("/auth/login", Login(sessionService, db))
|
||||
r.Post("/auth/refresh", RefreshToken(sessionService))
|
||||
r.Post("/auth/login", handler.Login(sessionService))
|
||||
r.Post("/auth/refresh", handler.RefreshToken(sessionService))
|
||||
})
|
||||
|
||||
// Protected routes (authentication required)
|
||||
r.Group(func(r chi.Router) {
|
||||
// Apply authentication middleware to all routes in this group
|
||||
r.Use(authMiddleware.Authenticate)
|
||||
r.Use(WithHandlerContext(db))
|
||||
r.Use(middleware.WithHandlerContext(db))
|
||||
|
||||
// Auth routes
|
||||
r.Post("/auth/logout", Logout(sessionService))
|
||||
r.Get("/auth/me", handler.GetCurrentUser(db))
|
||||
r.Post("/auth/logout", handler.Logout(sessionService))
|
||||
r.Get("/auth/me", handler.GetCurrentUser())
|
||||
|
||||
// Admin-only routes
|
||||
r.Group(func(r chi.Router) {
|
||||
@@ -41,35 +43,35 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddlew
|
||||
|
||||
// Workspace routes
|
||||
r.Route("/workspaces", func(r chi.Router) {
|
||||
r.Get("/", handler.ListWorkspaces(db))
|
||||
r.Post("/", handler.CreateWorkspace(db, fs))
|
||||
r.Get("/last", handler.GetLastWorkspace(db))
|
||||
r.Put("/last", handler.UpdateLastWorkspace(db))
|
||||
r.Get("/", handler.ListWorkspaces())
|
||||
r.Post("/", handler.CreateWorkspace())
|
||||
r.Get("/last", handler.GetLastWorkspace())
|
||||
r.Put("/last", handler.UpdateLastWorkspace())
|
||||
|
||||
// Single workspace routes
|
||||
r.Route("/{workspaceId}", func(r chi.Router) {
|
||||
r.Use(authMiddleware.RequireWorkspaceOwnership(db))
|
||||
r.Use(authMiddleware.RequireWorkspaceAccess)
|
||||
|
||||
r.Get("/", handler.GetWorkspace(db))
|
||||
r.Put("/", handler.UpdateWorkspace(db, fs))
|
||||
r.Delete("/", handler.DeleteWorkspace(db))
|
||||
r.Get("/", handler.GetWorkspace())
|
||||
r.Put("/", handler.UpdateWorkspace())
|
||||
r.Delete("/", handler.DeleteWorkspace())
|
||||
|
||||
// File routes
|
||||
r.Route("/files", func(r chi.Router) {
|
||||
r.Get("/", handler.ListFiles(fs))
|
||||
r.Get("/last", handler.GetLastOpenedFile(db, fs))
|
||||
r.Put("/last", handler.UpdateLastOpenedFile(db, fs))
|
||||
r.Get("/lookup", handler.LookupFileByName(fs))
|
||||
r.Get("/", handler.ListFiles())
|
||||
r.Get("/last", handler.GetLastOpenedFile())
|
||||
r.Put("/last", handler.UpdateLastOpenedFile())
|
||||
r.Get("/lookup", handler.LookupFileByName())
|
||||
|
||||
r.Post("/*", handler.SaveFile(fs))
|
||||
r.Get("/*", handler.GetFileContent(fs))
|
||||
r.Delete("/*", handler.DeleteFile(fs))
|
||||
r.Post("/*", handler.SaveFile())
|
||||
r.Get("/*", handler.GetFileContent())
|
||||
r.Delete("/*", handler.DeleteFile())
|
||||
})
|
||||
|
||||
// Git routes
|
||||
r.Route("/git", func(r chi.Router) {
|
||||
r.Post("/commit", handler.StageCommitAndPush(fs))
|
||||
r.Post("/pull", handler.PullChanges(fs))
|
||||
r.Post("/commit", handler.StageCommitAndPush())
|
||||
r.Post("/pull", handler.PullChanges())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"novamd/internal/httpcontext"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
@@ -95,9 +97,8 @@ func (m *Middleware) RequireRole(role string) func(http.Handler) http.Handler {
|
||||
func (m *Middleware) RequireWorkspaceAccess(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get our handler context
|
||||
ctx := context.GetHandlerContext(r)
|
||||
if ctx == nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/httpcontext"
|
||||
"novamd/internal/models"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -31,7 +31,7 @@ type RefreshResponse struct {
|
||||
}
|
||||
|
||||
// Login handles user authentication and returns JWT tokens
|
||||
func Login(authService *auth.SessionService, db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) Login(authService *auth.SessionService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -46,7 +46,7 @@ func Login(authService *auth.SessionService, db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
user, err := db.GetUserByEmail(req.Email)
|
||||
user, err := h.DB.GetUserByEmail(req.Email)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
@@ -79,7 +79,7 @@ func Login(authService *auth.SessionService, db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Logout invalidates the user's session
|
||||
func Logout(authService *auth.SessionService) http.HandlerFunc {
|
||||
func (h *Handler) Logout(authService *auth.SessionService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
sessionID := r.Header.Get("X-Session-ID")
|
||||
if sessionID == "" {
|
||||
@@ -98,7 +98,7 @@ func Logout(authService *auth.SessionService) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// RefreshToken generates a new access token using a refresh token
|
||||
func RefreshToken(authService *auth.SessionService) http.HandlerFunc {
|
||||
func (h *Handler) RefreshToken(authService *auth.SessionService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req RefreshRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -127,15 +127,15 @@ func RefreshToken(authService *auth.SessionService) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// GetCurrentUser returns the currently authenticated user
|
||||
func (h *BaseHandler) GetCurrentUser(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) GetCurrentUser() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
user, err := db.GetUserByID(ctx.UserID)
|
||||
user, err := h.DB.GetUserByID(ctx.UserID)
|
||||
if err != nil {
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -1,24 +1,23 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/httpcontext"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) ListFiles(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) ListFiles() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
files, err := fs.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
||||
files, err := h.FS.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list files", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -28,9 +27,9 @@ func (h *BaseHandler) ListFiles(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) LookupFileByName(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) LookupFileByName() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -41,7 +40,7 @@ func (h *BaseHandler) LookupFileByName(fs *filesystem.FileSystem) http.HandlerFu
|
||||
return
|
||||
}
|
||||
|
||||
filePaths, err := fs.FindFileByName(ctx.UserID, ctx.Workspace.ID, filename)
|
||||
filePaths, err := h.FS.FindFileByName(ctx.UserID, ctx.Workspace.ID, filename)
|
||||
if err != nil {
|
||||
http.Error(w, "File not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -51,15 +50,15 @@ func (h *BaseHandler) LookupFileByName(fs *filesystem.FileSystem) http.HandlerFu
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) GetFileContent(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) GetFileContent() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := chi.URLParam(r, "*")
|
||||
content, err := fs.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
content, err := h.FS.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read file", http.StatusNotFound)
|
||||
return
|
||||
@@ -70,9 +69,9 @@ func (h *BaseHandler) GetFileContent(fs *filesystem.FileSystem) http.HandlerFunc
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SaveFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) SaveFile() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -84,7 +83,7 @@ func (h *BaseHandler) SaveFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
err = fs.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
||||
err = h.FS.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -94,15 +93,15 @@ func (h *BaseHandler) SaveFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) DeleteFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) DeleteFile() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
filePath := chi.URLParam(r, "*")
|
||||
err := fs.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
err := h.FS.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -113,20 +112,20 @@ func (h *BaseHandler) DeleteFile(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) GetLastOpenedFile(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) GetLastOpenedFile() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
filePath, err := db.GetLastOpenedFile(ctx.Workspace.ID)
|
||||
filePath, err := h.DB.GetLastOpenedFile(ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get last opened file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := fs.ValidatePath(ctx.UserID, ctx.Workspace.ID, filePath); err != nil {
|
||||
if _, err := h.FS.ValidatePath(ctx.UserID, ctx.Workspace.ID, filePath); err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -135,9 +134,9 @@ func (h *BaseHandler) GetLastOpenedFile(db *db.DB, fs *filesystem.FileSystem) ht
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) UpdateLastOpenedFile(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -153,13 +152,13 @@ func (h *BaseHandler) UpdateLastOpenedFile(db *db.DB, fs *filesystem.FileSystem)
|
||||
|
||||
// Validate the file path exists in the workspace
|
||||
if requestBody.FilePath != "" {
|
||||
if _, err := fs.ValidatePath(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
if _, err := h.FS.ValidatePath(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.UpdateLastOpenedFile(ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
if err := h.DB.UpdateLastOpenedFile(ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
http.Error(w, "Failed to update last opened file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/httpcontext"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) StageCommitAndPush(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) StageCommitAndPush() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func (h *BaseHandler) StageCommitAndPush(fs *filesystem.FileSystem) http.Handler
|
||||
return
|
||||
}
|
||||
|
||||
err := fs.StageCommitAndPush(ctx.UserID, ctx.Workspace.ID, requestBody.Message)
|
||||
err := h.FS.StageCommitAndPush(ctx.UserID, ctx.Workspace.ID, requestBody.Message)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to stage, commit, and push changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -38,14 +38,14 @@ func (h *BaseHandler) StageCommitAndPush(fs *filesystem.FileSystem) http.Handler
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) PullChanges(fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) PullChanges() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := fs.Pull(ctx.UserID, ctx.Workspace.ID)
|
||||
err := h.FS.Pull(ctx.UserID, ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to pull changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
30
backend/internal/handlers/handlers.go
Normal file
30
backend/internal/handlers/handlers.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
)
|
||||
|
||||
// Handler provides common functionality for all handlers
|
||||
type Handler struct {
|
||||
DB *db.DB
|
||||
FS *filesystem.FileSystem
|
||||
}
|
||||
|
||||
// NewHandler creates a new handler with the given dependencies
|
||||
func NewHandler(db *db.DB, fs *filesystem.FileSystem) *Handler {
|
||||
return &Handler{
|
||||
DB: db,
|
||||
FS: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// respondJSON is a helper to send JSON responses
|
||||
func respondJSON(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -1,19 +1,19 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/httpcontext"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) GetUser(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) GetUser() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := db.GetUserByID(ctx.UserID)
|
||||
user, err := h.DB.GetUserByID(ctx.UserID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get user", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -1,22 +1,21 @@
|
||||
package api
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/httpcontext"
|
||||
"novamd/internal/models"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) ListWorkspaces(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) ListWorkspaces() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
workspaces, err := db.GetWorkspacesByUserID(ctx.UserID)
|
||||
workspaces, err := h.DB.GetWorkspacesByUserID(ctx.UserID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list workspaces", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -26,9 +25,9 @@ func (h *BaseHandler) ListWorkspaces(db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) CreateWorkspace(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) CreateWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -40,12 +39,12 @@ func (h *BaseHandler) CreateWorkspace(db *db.DB, fs *filesystem.FileSystem) http
|
||||
}
|
||||
|
||||
workspace.UserID = ctx.UserID
|
||||
if err := db.CreateWorkspace(&workspace); err != nil {
|
||||
if err := h.DB.CreateWorkspace(&workspace); err != nil {
|
||||
http.Error(w, "Failed to create workspace", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := fs.InitializeUserWorkspace(workspace.UserID, workspace.ID); err != nil {
|
||||
if err := h.FS.InitializeUserWorkspace(workspace.UserID, workspace.ID); err != nil {
|
||||
http.Error(w, "Failed to initialize workspace directory", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -54,9 +53,9 @@ func (h *BaseHandler) CreateWorkspace(db *db.DB, fs *filesystem.FileSystem) http
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) GetWorkspace(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) GetWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -81,9 +80,9 @@ func gitSettingsChanged(new, old *models.Workspace) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *BaseHandler) UpdateWorkspace(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc {
|
||||
func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -107,7 +106,7 @@ func (h *BaseHandler) UpdateWorkspace(db *db.DB, fs *filesystem.FileSystem) http
|
||||
// Handle Git repository setup/teardown if Git settings changed
|
||||
if gitSettingsChanged(&workspace, ctx.Workspace) {
|
||||
if workspace.GitEnabled {
|
||||
if err := fs.SetupGitRepo(
|
||||
if err := h.FS.SetupGitRepo(
|
||||
ctx.UserID,
|
||||
ctx.Workspace.ID,
|
||||
workspace.GitURL,
|
||||
@@ -119,11 +118,11 @@ func (h *BaseHandler) UpdateWorkspace(db *db.DB, fs *filesystem.FileSystem) http
|
||||
}
|
||||
|
||||
} else {
|
||||
fs.DisableGitRepo(ctx.UserID, ctx.Workspace.ID)
|
||||
h.FS.DisableGitRepo(ctx.UserID, ctx.Workspace.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.UpdateWorkspace(&workspace); err != nil {
|
||||
if err := h.DB.UpdateWorkspace(&workspace); err != nil {
|
||||
http.Error(w, "Failed to update workspace", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -132,15 +131,15 @@ func (h *BaseHandler) UpdateWorkspace(db *db.DB, fs *filesystem.FileSystem) http
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) DeleteWorkspace(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) DeleteWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is the user's last workspace
|
||||
workspaces, err := db.GetWorkspacesByUserID(ctx.UserID)
|
||||
workspaces, err := h.DB.GetWorkspacesByUserID(ctx.UserID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get workspaces", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -161,7 +160,7 @@ func (h *BaseHandler) DeleteWorkspace(db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Start transaction
|
||||
tx, err := db.Begin()
|
||||
tx, err := h.DB.Begin()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to start transaction", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -169,14 +168,14 @@ func (h *BaseHandler) DeleteWorkspace(db *db.DB) http.HandlerFunc {
|
||||
defer tx.Rollback()
|
||||
|
||||
// Update last workspace ID first
|
||||
err = db.UpdateLastWorkspaceTx(tx, ctx.UserID, nextWorkspaceID)
|
||||
err = h.DB.UpdateLastWorkspaceTx(tx, ctx.UserID, nextWorkspaceID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to update last workspace", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the workspace
|
||||
err = db.DeleteWorkspaceTx(tx, ctx.Workspace.ID)
|
||||
err = h.DB.DeleteWorkspaceTx(tx, ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to delete workspace", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -193,14 +192,14 @@ func (h *BaseHandler) DeleteWorkspace(db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) GetLastWorkspace(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) GetLastWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceID, err := db.GetLastWorkspaceID(ctx.UserID)
|
||||
workspaceID, err := h.DB.GetLastWorkspaceID(ctx.UserID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get last workspace", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -210,9 +209,9 @@ func (h *BaseHandler) GetLastWorkspace(db *db.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) UpdateLastWorkspace(db *db.DB) http.HandlerFunc {
|
||||
func (h *Handler) UpdateLastWorkspace() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := h.getContext(w, r)
|
||||
ctx, ok := httpcontext.GetRequestContext(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -226,7 +225,7 @@ func (h *BaseHandler) UpdateLastWorkspace(db *db.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.UpdateLastWorkspace(ctx.UserID, requestBody.WorkspaceID); err != nil {
|
||||
if err := h.DB.UpdateLastWorkspace(ctx.UserID, requestBody.WorkspaceID); err != nil {
|
||||
http.Error(w, "Failed to update last workspace", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
31
backend/internal/httpcontext/context.go
Normal file
31
backend/internal/httpcontext/context.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package httpcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"novamd/internal/models"
|
||||
)
|
||||
|
||||
// HandlerContext holds the request-specific data available to all handlers
|
||||
type HandlerContext struct {
|
||||
UserID int
|
||||
UserRole string
|
||||
Workspace *models.Workspace // Will be nil for non-workspace endpoints
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
const HandlerContextKey contextKey = "handlerContext"
|
||||
|
||||
func GetRequestContext(w http.ResponseWriter, r *http.Request) (*HandlerContext, bool) {
|
||||
ctx := r.Context().Value(HandlerContextKey)
|
||||
if ctx == nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return nil, false
|
||||
}
|
||||
return ctx.(*HandlerContext), true
|
||||
}
|
||||
|
||||
func WithHandlerContext(r *http.Request, hctx *HandlerContext) *http.Request {
|
||||
return r.WithContext(context.WithValue(r.Context(), HandlerContextKey, hctx))
|
||||
}
|
||||
49
backend/internal/middleware/context.go
Normal file
49
backend/internal/middleware/context.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/httpcontext"
|
||||
"novamd/internal/models"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// WithHandlerContext middleware populates the HandlerContext for the request
|
||||
// This should be placed after authentication middleware
|
||||
func WithHandlerContext(db *db.DB) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get user claims from auth middleware
|
||||
claims, err := auth.GetUserFromContext(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Try to get workspace from URL if it exists
|
||||
workspaceName := chi.URLParam(r, "workspaceName")
|
||||
|
||||
var workspace *models.Workspace
|
||||
if workspaceName != "" {
|
||||
workspace, err = db.GetWorkspaceByName(claims.UserID, workspaceName)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create handler context with user and workspace info
|
||||
hctx := &httpcontext.HandlerContext{
|
||||
UserID: claims.UserID,
|
||||
UserRole: claims.Role,
|
||||
Workspace: workspace,
|
||||
}
|
||||
|
||||
// Add context to request
|
||||
r = httpcontext.WithHandlerContext(r, hctx)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user