198 lines
4.9 KiB
Go
198 lines
4.9 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
)
|
||
|
|
|
||
|
|
// User represents a row in the users table.
|
||
|
|
type User struct {
|
||
|
|
UserID string
|
||
|
|
Name string
|
||
|
|
RootObjectID string
|
||
|
|
}
|
||
|
|
|
||
|
|
// CreateUser inserts a new user into the users table.
|
||
|
|
func CreateUser(userID, name, rootObjectID string) error {
|
||
|
|
_, err := DB.Exec(
|
||
|
|
"INSERT INTO users (userId, name, rootObjectId) VALUES (?, ?, ?)",
|
||
|
|
userID, name, rootObjectID,
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to create user: %w", err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetUser retrieves a user by ID.
|
||
|
|
func GetUser(userID string) (User, error) {
|
||
|
|
var user User
|
||
|
|
err := DB.QueryRow(
|
||
|
|
"SELECT userId, name, rootObjectId FROM users WHERE userId = ?",
|
||
|
|
userID,
|
||
|
|
).Scan(&user.UserID, &user.Name, &user.RootObjectID)
|
||
|
|
if err != nil {
|
||
|
|
if err == sql.ErrNoRows {
|
||
|
|
return User{}, fmt.Errorf("user not found")
|
||
|
|
}
|
||
|
|
return User{}, fmt.Errorf("failed to get user: %w", err)
|
||
|
|
}
|
||
|
|
return user, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// UpdateUser updates the display name for a user.
|
||
|
|
func UpdateUser(userID, name string) error {
|
||
|
|
result, err := DB.Exec(
|
||
|
|
"UPDATE users SET name = ? WHERE userId = ?",
|
||
|
|
name, userID,
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to update user: %w", err)
|
||
|
|
}
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to check update result: %w", err)
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return fmt.Errorf("user not found")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DeleteObjectsByUser removes all objects belonging to a user.
|
||
|
|
func DeleteObjectsByUser(userID string) error {
|
||
|
|
_, err := DB.Exec("DELETE FROM objects WHERE userId = ?", userID)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to delete user objects: %w", err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DeleteUser removes a user by ID and all associated objects.
|
||
|
|
func DeleteUser(userID string) error {
|
||
|
|
if err := DeleteObjectsByUser(userID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
result, err := DB.Exec("DELETE FROM users WHERE userId = ?", userID)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to delete user: %w", err)
|
||
|
|
}
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to check delete result: %w", err)
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return fmt.Errorf("user not found")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// createUserRequest is the expected body for POST /users.
|
||
|
|
type createUserRequest struct {
|
||
|
|
UserID string `json:"userId" binding:"required"`
|
||
|
|
Name string `json:"name" binding:"required"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func createUserHandler(c *gin.Context) {
|
||
|
|
var req createUserRequest
|
||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
rootObjectID := uuid.NewString()
|
||
|
|
rootObjectData := `{"type":"root","childIds":[]}`
|
||
|
|
|
||
|
|
if err := CreateObject(rootObjectID, req.UserID, rootObjectData); err != nil {
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := CreateUser(req.UserID, req.Name, rootObjectID); err != nil {
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
c.JSON(http.StatusCreated, gin.H{"status": "created", "rootObjectId": rootObjectID})
|
||
|
|
}
|
||
|
|
|
||
|
|
func getUserHandler(c *gin.Context) {
|
||
|
|
id := c.Param("id")
|
||
|
|
user, err := GetUser(id)
|
||
|
|
if err != nil {
|
||
|
|
if err.Error() == "user not found" {
|
||
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
objects, err := GetObjectsByUser(id)
|
||
|
|
if err != nil {
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return objects with camelCase keys to match the frontend contract.
|
||
|
|
formattedObjects := make([]gin.H, 0, len(objects))
|
||
|
|
for _, obj := range objects {
|
||
|
|
formattedObjects = append(formattedObjects, gin.H{
|
||
|
|
"objectId": obj.ObjectID,
|
||
|
|
"userId": obj.UserID,
|
||
|
|
"objectData": obj.ObjectData,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
c.JSON(http.StatusOK, gin.H{
|
||
|
|
"userId": user.UserID,
|
||
|
|
"name": user.Name,
|
||
|
|
"rootObjectId": user.RootObjectID,
|
||
|
|
"objects": formattedObjects,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// updateUserRequest is the expected body for PUT /users/:id.
|
||
|
|
type updateUserRequest struct {
|
||
|
|
Name string `json:"name" binding:"required"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func updateUserHandler(c *gin.Context) {
|
||
|
|
id := c.Param("id")
|
||
|
|
var req updateUserRequest
|
||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := UpdateUser(id, req.Name); err != nil {
|
||
|
|
if err.Error() == "user not found" {
|
||
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
c.JSON(http.StatusOK, gin.H{"status": "updated"})
|
||
|
|
}
|
||
|
|
|
||
|
|
func deleteUserHandler(c *gin.Context) {
|
||
|
|
id := c.Param("id")
|
||
|
|
if err := DeleteUser(id); err != nil {
|
||
|
|
if err.Error() == "user not found" {
|
||
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
|
||
|
|
}
|