opencode: delete method for stacks
This commit is contained in:
@ -133,29 +133,29 @@ func (client AgentClient) getRequest(body []byte) (*http.Request, error) {
|
|||||||
func (client AgentClient) Request(req *AgentRequestBody) (AgentResponse, error) {
|
func (client AgentClient) Request(req *AgentRequestBody) (AgentResponse, error) {
|
||||||
jsonAiRequest, err := json.Marshal(req)
|
jsonAiRequest, err := json.Marshal(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AgentResponse{}, fmt.Errorf("Could not format JSON", err)
|
return AgentResponse{}, fmt.Errorf("Could not format JSON: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRequest, err := client.getRequest(jsonAiRequest)
|
httpRequest, err := client.getRequest(jsonAiRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AgentResponse{}, fmt.Errorf("Could not get request", err)
|
return AgentResponse{}, fmt.Errorf("Could not get request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(httpRequest)
|
resp, err := client.Do(httpRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AgentResponse{}, fmt.Errorf("Could not send request", err)
|
return AgentResponse{}, fmt.Errorf("Could not send request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := io.ReadAll(resp.Body)
|
response, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AgentResponse{}, fmt.Errorf("Could not read body", err)
|
return AgentResponse{}, fmt.Errorf("Could not read body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
agentResponse := AgentResponse{}
|
agentResponse := AgentResponse{}
|
||||||
err = json.Unmarshal(response, &agentResponse)
|
err = json.Unmarshal(response, &agentResponse)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AgentResponse{}, fmt.Errorf("Could not unmarshal response, response: %s", string(response), err)
|
return AgentResponse{}, fmt.Errorf("Could not unmarshal response, response: %s: %w", string(response), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(agentResponse.Choices) != 1 {
|
if len(agentResponse.Choices) != 1 {
|
||||||
|
@ -354,6 +354,163 @@ func TestAllRoutes(t *testing.T) {
|
|||||||
t.Errorf("Expected status 400 for invalid ID, got %d", resp.StatusCode)
|
t.Errorf("Expected status 400 for invalid ID, got %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Delete stack without authentication", func(t *testing.T) {
|
||||||
|
fakeUUID := uuid.New()
|
||||||
|
resp := tc.makeRequest(t, "DELETE", "/stacks/"+fakeUUID.String(), "", nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Errorf("Expected status 401 for unauthenticated delete, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete stack with invalid ID", func(t *testing.T) {
|
||||||
|
resp := tc.makeRequest(t, "DELETE", "/stacks/invalid-id", stackUser.Token, nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Errorf("Expected status 400 for invalid ID, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete non-existent stack", func(t *testing.T) {
|
||||||
|
fakeUUID := uuid.New()
|
||||||
|
resp := tc.makeRequest(t, "DELETE", "/stacks/"+fakeUUID.String(), stackUser.Token, nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Errorf("Expected status 400 for non-existent stack, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Create and delete stack successfully", func(t *testing.T) {
|
||||||
|
// First create a stack
|
||||||
|
stackData := map[string]string{
|
||||||
|
"title": "Stack to Delete",
|
||||||
|
"fields": "name,description,value",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := tc.makeJSONRequest(t, "POST", "/stacks/", stackUser.Token, stackData)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Failed to create stack for deletion test, got %d", resp.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of stacks to find the created stack ID
|
||||||
|
resp = tc.makeRequest(t, "GET", "/stacks/", stackUser.Token, nil)
|
||||||
|
|
||||||
|
var stacks []map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&stacks); err != nil {
|
||||||
|
t.Errorf("Failed to decode stacks response: %v", err)
|
||||||
|
resp.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if len(stacks) == 0 {
|
||||||
|
t.Errorf("No stacks found after creation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the stack we just created
|
||||||
|
var stackToDelete map[string]interface{}
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if name, ok := stack["Name"].(string); ok && name == "Stack to Delete" {
|
||||||
|
stackToDelete = stack
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stackToDelete == nil {
|
||||||
|
t.Errorf("Could not find created stack")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stackID, ok := stackToDelete["ID"].(string)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Stack ID not found or not a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now delete the stack
|
||||||
|
resp = tc.makeRequest(t, "DELETE", "/stacks/"+stackID, stackUser.Token, nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Expected status 200 for successful delete, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the stack is gone by trying to get it again
|
||||||
|
resp = tc.makeRequest(t, "GET", "/stacks/", stackUser.Token, nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var stacksAfterDelete []map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&stacksAfterDelete); err != nil {
|
||||||
|
t.Errorf("Failed to decode stacks response after delete: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the deleted stack is no longer in the list
|
||||||
|
for _, stack := range stacksAfterDelete {
|
||||||
|
if id, ok := stack["ID"].(string); ok && id == stackID {
|
||||||
|
t.Errorf("Stack still exists after deletion")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete stack belonging to different user", func(t *testing.T) {
|
||||||
|
// Create a stack with stackUser
|
||||||
|
stackData := map[string]string{
|
||||||
|
"title": "Other User's Stack",
|
||||||
|
"fields": "name,description,value",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := tc.makeJSONRequest(t, "POST", "/stacks/", stackUser.Token, stackData)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Failed to create stack for ownership test, got %d", resp.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stack ID
|
||||||
|
resp = tc.makeRequest(t, "GET", "/stacks/", stackUser.Token, nil)
|
||||||
|
|
||||||
|
var stacks []map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&stacks); err != nil {
|
||||||
|
t.Errorf("Failed to decode stacks response: %v", err)
|
||||||
|
resp.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
var stackID string
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if name, ok := stack["Name"].(string); ok && name == "Other User's Stack" {
|
||||||
|
if id, ok := stack["ID"].(string); ok {
|
||||||
|
stackID = id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stackID == "" {
|
||||||
|
t.Errorf("Could not find created stack ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to delete the stack with a different user (imageUser)
|
||||||
|
resp = tc.makeRequest(t, "DELETE", "/stacks/"+stackID, imageUser.Token, nil)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Errorf("Expected status 400 when deleting another user's stack, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Image Routes", func(t *testing.T) {
|
t.Run("Image Routes", func(t *testing.T) {
|
||||||
|
@ -1,78 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"screenmark/screenmark/agents/client"
|
|
||||||
"screenmark/screenmark/auth"
|
|
||||||
"screenmark/screenmark/images"
|
|
||||||
"screenmark/screenmark/models"
|
"screenmark/screenmark/models"
|
||||||
"screenmark/screenmark/stacks"
|
|
||||||
|
|
||||||
ourmiddleware "screenmark/screenmark/middleware"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestAiClient struct {
|
|
||||||
ImageInfo client.ImageMessageContent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client TestAiClient) GetImageInfo(imageName string, imageData []byte) (client.ImageMessageContent, error) {
|
|
||||||
return client.ImageInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRouter(db *sql.DB) chi.Router {
|
|
||||||
imageModel := models.NewImageModel(db)
|
|
||||||
stackModel := models.NewListModel(db)
|
|
||||||
|
|
||||||
stackHandler := stacks.CreateStackHandler(db)
|
|
||||||
authHandler := auth.CreateAuthHandler(db)
|
|
||||||
imageHandler := images.CreateImageHandler(db)
|
|
||||||
|
|
||||||
notifier := NewNotifier[Notification](10)
|
|
||||||
|
|
||||||
// Only start event listeners if not in test environment
|
|
||||||
if os.Getenv("GO_TEST_ENVIRONMENT") != "true" {
|
|
||||||
|
|
||||||
// TODO: should extract these into a notification manager
|
|
||||||
// And actually make them the same code.
|
|
||||||
// The events are basically the same.
|
|
||||||
|
|
||||||
go ListenNewImageEvents(db)
|
|
||||||
go ListenProcessingImageStatus(db, imageModel, ¬ifier)
|
|
||||||
go ListenNewStackEvents(db)
|
|
||||||
go ListenProcessingStackStatus(db, stackModel, ¬ifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := chi.NewRouter()
|
|
||||||
|
|
||||||
r.Use(middleware.Logger)
|
|
||||||
r.Use(ourmiddleware.CorsMiddleware)
|
|
||||||
|
|
||||||
r.Route("/stacks", stackHandler.CreateRoutes)
|
|
||||||
r.Route("/auth", authHandler.CreateRoutes)
|
|
||||||
r.Route("/images", imageHandler.CreateRoutes)
|
|
||||||
|
|
||||||
r.Route("/notifications", func(r chi.Router) {
|
|
||||||
r.Use(ourmiddleware.GetUserIdFromUrl)
|
|
||||||
|
|
||||||
r.Get("/", CreateEventsHandler(¬ifier))
|
|
||||||
})
|
|
||||||
|
|
||||||
logWriter := DatabaseWriter{
|
|
||||||
dbPool: db,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Route("/logs", createLogHandler(&logWriter))
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,7 +38,7 @@ type UserProcessingImage struct {
|
|||||||
func (m ImageModel) Process(ctx context.Context, userId uuid.UUID, image model.Image) (model.UserImagesToProcess, error) {
|
func (m ImageModel) Process(ctx context.Context, userId uuid.UUID, image model.Image) (model.UserImagesToProcess, error) {
|
||||||
tx, err := m.dbPool.BeginTx(ctx, nil)
|
tx, err := m.dbPool.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.UserImagesToProcess{}, fmt.Errorf("Failed to begin transaction", err)
|
return model.UserImagesToProcess{}, fmt.Errorf("Failed to begin transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertImageStmt := Image.
|
insertImageStmt := Image.
|
||||||
@ -49,7 +49,7 @@ func (m ImageModel) Process(ctx context.Context, userId uuid.UUID, image model.I
|
|||||||
insertedImage := model.Image{}
|
insertedImage := model.Image{}
|
||||||
err = insertImageStmt.QueryContext(ctx, tx, &insertedImage)
|
err = insertImageStmt.QueryContext(ctx, tx, &insertedImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.UserImagesToProcess{}, fmt.Errorf("Could not insert/query new image. SQL %s.", insertImageStmt.DebugSql(), err)
|
return model.UserImagesToProcess{}, fmt.Errorf("Could not insert/query new image. SQL %s: %w", insertImageStmt.DebugSql(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := UserImagesToProcess.
|
stmt := UserImagesToProcess.
|
||||||
@ -60,7 +60,7 @@ func (m ImageModel) Process(ctx context.Context, userId uuid.UUID, image model.I
|
|||||||
userImage := model.UserImagesToProcess{}
|
userImage := model.UserImagesToProcess{}
|
||||||
err = stmt.QueryContext(ctx, tx, &userImage)
|
err = stmt.QueryContext(ctx, tx, &userImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.UserImagesToProcess{}, fmt.Errorf("Could not insert user_image", err)
|
return model.UserImagesToProcess{}, fmt.Errorf("Could not insert user_image: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
|
@ -247,6 +247,64 @@ func (m ListModel) SaveProcessing(ctx context.Context, userID uuid.UUID, title s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// DELETE methods
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
func (m ListModel) Delete(ctx context.Context, listID uuid.UUID, userID uuid.UUID) error {
|
||||||
|
// First verify the list belongs to the user
|
||||||
|
checkOwnershipStmt := Lists.
|
||||||
|
SELECT(Lists.ID).
|
||||||
|
WHERE(Lists.ID.EQ(UUID(listID)).AND(Lists.UserID.EQ(UUID(userID))))
|
||||||
|
|
||||||
|
var existingList model.Lists
|
||||||
|
err := checkOwnershipStmt.QueryContext(ctx, m.dbPool, &existingList)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not verify list ownership: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a transaction to ensure all deletions happen atomically
|
||||||
|
tx, err := m.dbPool.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Delete in reverse order of dependencies:
|
||||||
|
// 1. Delete schema items first
|
||||||
|
deleteSchemaItemsStmt := SchemaItems.DELETE().
|
||||||
|
WHERE(SchemaItems.SchemaID.IN(
|
||||||
|
Schemas.SELECT(Schemas.ID).
|
||||||
|
WHERE(Schemas.ListID.EQ(UUID(listID))),
|
||||||
|
))
|
||||||
|
_, err = deleteSchemaItemsStmt.ExecContext(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete schema items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Delete schemas
|
||||||
|
deleteSchemasStmt := Schemas.DELETE().WHERE(Schemas.ListID.EQ(UUID(listID)))
|
||||||
|
_, err = deleteSchemasStmt.ExecContext(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete schemas: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Delete the list itself
|
||||||
|
deleteListStmt := Lists.DELETE().WHERE(Lists.ID.EQ(UUID(listID)))
|
||||||
|
_, err = deleteListStmt.ExecContext(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewListModel(db *sql.DB) ListModel {
|
func NewListModel(db *sql.DB) ListModel {
|
||||||
return ListModel{dbPool: db}
|
return ListModel{dbPool: db}
|
||||||
}
|
}
|
||||||
|
71
backend/router.go
Normal file
71
backend/router.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
"screenmark/screenmark/agents/client"
|
||||||
|
"screenmark/screenmark/auth"
|
||||||
|
"screenmark/screenmark/images"
|
||||||
|
"screenmark/screenmark/models"
|
||||||
|
"screenmark/screenmark/stacks"
|
||||||
|
|
||||||
|
ourmiddleware "screenmark/screenmark/middleware"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestAiClient struct {
|
||||||
|
ImageInfo client.ImageMessageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client TestAiClient) GetImageInfo(imageName string, imageData []byte) (client.ImageMessageContent, error) {
|
||||||
|
return client.ImageInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRouter(db *sql.DB) chi.Router {
|
||||||
|
imageModel := models.NewImageModel(db)
|
||||||
|
stackModel := models.NewListModel(db)
|
||||||
|
|
||||||
|
stackHandler := stacks.CreateStackHandler(db)
|
||||||
|
authHandler := auth.CreateAuthHandler(db)
|
||||||
|
imageHandler := images.CreateImageHandler(db)
|
||||||
|
|
||||||
|
notifier := NewNotifier[Notification](10)
|
||||||
|
|
||||||
|
// Only start event listeners if not in test environment
|
||||||
|
if os.Getenv("GO_TEST_ENVIRONMENT") != "true" {
|
||||||
|
|
||||||
|
// TODO: should extract these into a notification manager
|
||||||
|
// And actually make them the same code.
|
||||||
|
// The events are basically the same.
|
||||||
|
|
||||||
|
go ListenNewImageEvents(db)
|
||||||
|
go ListenProcessingImageStatus(db, imageModel, ¬ifier)
|
||||||
|
go ListenNewStackEvents(db)
|
||||||
|
go ListenProcessingStackStatus(db, stackModel, ¬ifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(ourmiddleware.CorsMiddleware)
|
||||||
|
|
||||||
|
r.Route("/stacks", stackHandler.CreateRoutes)
|
||||||
|
r.Route("/auth", authHandler.CreateRoutes)
|
||||||
|
r.Route("/images", imageHandler.CreateRoutes)
|
||||||
|
|
||||||
|
r.Route("/notifications", func(r chi.Router) {
|
||||||
|
r.Use(ourmiddleware.GetUserIdFromUrl)
|
||||||
|
|
||||||
|
r.Get("/", CreateEventsHandler(¬ifier))
|
||||||
|
})
|
||||||
|
|
||||||
|
logWriter := DatabaseWriter{
|
||||||
|
dbPool: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Route("/logs", createLogHandler(&logWriter))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
@ -2,10 +2,13 @@ package stacks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
. "screenmark/screenmark/.gen/haystack/haystack/model"
|
||||||
"screenmark/screenmark/middleware"
|
"screenmark/screenmark/middleware"
|
||||||
"screenmark/screenmark/models"
|
"screenmark/screenmark/models"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@ -66,6 +69,29 @@ func (h *StackHandler) editStack(req EditStack, w http.ResponseWriter, r *http.R
|
|||||||
w.WriteHeader(http.StatusNotImplemented)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
listID, err := middleware.GetPathParamID(h.logger, "listID", w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.stackModel.Delete(ctx, listID, userID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("could not delete stack", "err", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
type CreateStackBody struct {
|
type CreateStackBody struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|
||||||
@ -80,9 +106,27 @@ func (h *StackHandler) createStack(body CreateStackBody, w http.ResponseWriter,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.stackModel.SaveProcessing(ctx, userID, body.Title, body.Fields)
|
// Convert fields string to basic schema items
|
||||||
|
// For now, create a simple schema item for each field
|
||||||
|
var schemaItems []SchemaItems
|
||||||
|
if body.Fields != "" {
|
||||||
|
fields := strings.Split(body.Fields, ",")
|
||||||
|
for i, field := range fields {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
if field != "" {
|
||||||
|
schemaItems = append(schemaItems, SchemaItems{
|
||||||
|
Item: field,
|
||||||
|
Value: "",
|
||||||
|
Description: fmt.Sprintf("Field %d: %s", i+1, field),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use empty description for now since the API doesn't provide one
|
||||||
|
_, err = h.stackModel.Save(ctx, userID, body.Title, "", schemaItems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Warn("could not save processing", "err", err)
|
h.logger.Warn("could not save stack", "err", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -102,6 +146,7 @@ func (h *StackHandler) CreateRoutes(r chi.Router) {
|
|||||||
|
|
||||||
r.Post("/", middleware.WithValidatedPost(h.createStack))
|
r.Post("/", middleware.WithValidatedPost(h.createStack))
|
||||||
r.Patch("/{listID}", middleware.WithValidatedPost(h.editStack))
|
r.Patch("/{listID}", middleware.WithValidatedPost(h.editStack))
|
||||||
|
r.Delete("/{listID}", h.deleteStack)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user