package stacks import ( "database/sql" "net/http" "os" "screenmark/screenmark/.gen/haystack/haystack/model" "screenmark/screenmark/limits" "screenmark/screenmark/middleware" "screenmark/screenmark/models" "github.com/charmbracelet/log" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) type StackHandler struct { logger *log.Logger imageModel models.ImageModel stackModel models.StackModel limitsManager limits.LimitsManagerMethods jwtManager *middleware.JwtManager } func (h *StackHandler) getAllStacks(w http.ResponseWriter, r *http.Request) { ctx := r.Context() userID, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { return } stacks, err := h.stackModel.List(ctx, userID) if err != nil { h.logger.Warn("could not get stacks", "err", err) w.WriteHeader(http.StatusBadRequest) return } middleware.WriteJsonOrError(h.logger, stacks, w) } func (h *StackHandler) getStackItems(w http.ResponseWriter, r *http.Request) { ctx := r.Context() _, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { return } stackID, err := middleware.GetPathParamID(h.logger, "listID", w, r) if err != nil { return } // TODO: must check for permission here. lists, err := h.stackModel.ListItems(ctx, stackID) if err != nil { h.logger.Warn("could not get list items", "err", err) w.WriteHeader(http.StatusBadRequest) return } middleware.WriteJsonOrError(h.logger, lists, w) } type EditStack struct { Hello string `json:"hello"` } func (h *StackHandler) editStack(req EditStack, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } func (h *StackHandler) deleteStack(w http.ResponseWriter, r *http.Request) { ctx := r.Context() userID, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { return } stackID, err := middleware.GetPathParamID(h.logger, "listID", w, r) if err != nil { return } err = h.stackModel.Delete(ctx, stackID, userID) if err != nil { h.logger.Warn("could not delete stack", "err", err) w.WriteHeader(http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) } func (h *StackHandler) deleteImageFromStack(w http.ResponseWriter, r *http.Request) { ctx := r.Context() stringListID := chi.URLParam(r, "stackID") stringImageID := chi.URLParam(r, "imageID") imageID, err := uuid.Parse(stringImageID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } stackID, err := uuid.Parse(stringListID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // TODO: this should be extracted into a middleware of sorts userID, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { w.WriteHeader(http.StatusUnauthorized) return } stack, err := h.stackModel.Get(ctx, stackID) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if stack.UserID != userID { w.WriteHeader(http.StatusUnauthorized) return } err = h.stackModel.DeleteImage(ctx, stackID, imageID) if err != nil { h.logger.Warn("failed to delete image from list", "error", err) w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } func (h *StackHandler) deleteImageStackSchemaItem(w http.ResponseWriter, r *http.Request) { ctx := r.Context() stringStackID := chi.URLParam(r, "stackID") stringSchemaItemID := chi.URLParam(r, "schemaItemID") stackID, err := uuid.Parse(stringStackID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } schemaItemID, err := uuid.Parse(stringSchemaItemID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // TODO: this should be extracted into a middleware of sorts userID, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { w.WriteHeader(http.StatusUnauthorized) return } stack, err := h.stackModel.Get(ctx, stackID) if err != nil { h.logger.Error("could not get stack model", "err", err) w.WriteHeader(http.StatusInternalServerError) return } if stack.UserID != userID { w.WriteHeader(http.StatusUnauthorized) return } // TODO: // The code above is repeated, because it contains stack & image // manipulations. So we could create a middleware. // If you repeat this 3 times, then organise it :) err = h.stackModel.DeleteSchemaItem(ctx, stackID, schemaItemID) if err != nil { h.logger.Warn("failed to delete image from list", "error", err) w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } type CreateStackBody struct { Title string `json:"title"` Description string `json:"description"` } func (h *StackHandler) createStack(body CreateStackBody, w http.ResponseWriter, r *http.Request) { ctx := r.Context() userID, err := middleware.GetUserID(ctx, h.logger, w) if err != nil { return } // TODO: Add the stack processor here stack, err := h.stackModel.Save(ctx, userID, body.Title, body.Description, model.Progress_NotStarted) if err != nil { h.logger.Warn("could not save stack", "err", err) w.WriteHeader(http.StatusInternalServerError) return } middleware.WriteJsonOrError(h.logger, stack, w) } func (h *StackHandler) CreateRoutes(r chi.Router) { h.logger.Info("Mounting stack router") r.Group(func(r chi.Router) { r.Use(middleware.ProtectedRoute(h.jwtManager)) r.Use(middleware.SetJson) r.Get("/", h.getAllStacks) r.Get("/{stackID}", h.getStackItems) r.Post("/", middleware.WithLimit(h.logger, h.limitsManager.HasReachedStackLimit, middleware.WithValidatedPost(h.createStack))) r.Patch("/{stackID}", middleware.WithValidatedPost(h.editStack)) r.Delete("/{stackID}", h.deleteStack) r.Delete("/{stackID}/{imageID}", h.deleteImageFromStack) r.Delete("/{stackID}/{schemaItemID}", h.deleteImageStackSchemaItem) }) } func CreateStackHandler(db *sql.DB, limitsManager limits.LimitsManagerMethods, jwtManager *middleware.JwtManager) StackHandler { stackModel := models.NewStackModel(db) imageModel := models.NewImageModel(db) logger := log.New(os.Stdout).WithPrefix("Stacks") return StackHandler{ logger: logger, imageModel: imageModel, stackModel: stackModel, limitsManager: limitsManager, jwtManager: jwtManager, } }