package handlers import ( "context" "fmt" "io" "net/http" "strconv" "strings" "git.keircn.com/keiran/termcloud/internal/db" "github.com/labstack/echo/v4" ) type Handlers struct { bucketService *db.BucketService accountService *db.AccountService } func NewHandlers(bucketService *db.BucketService, accountService *db.AccountService) *Handlers { return &Handlers{ bucketService: bucketService, accountService: accountService, } } func (h *Handlers) RootHandler(c echo.Context) error { return c.JSON(200, map[string]string{ "service": "TermCloud Storage API", "version": "1.0.0", "docs": "https://github.com/termcloud/termcloud", }) } func (h *Handlers) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { accessToken := c.Request().Header.Get("X-Access-Token") if accessToken == "" { return c.JSON(401, map[string]string{"error": "Access token required"}) } account, err := h.accountService.GetAccountByToken(context.Background(), accessToken) if err != nil { return c.JSON(401, map[string]string{"error": "Invalid access token"}) } if !account.IsActive { return c.JSON(403, map[string]string{"error": "Account inactive - please add funds"}) } c.Set("account", account) return next(c) } } func (h *Handlers) CreateAccountHandler(c echo.Context) error { account, err := h.accountService.CreateAccount(context.Background()) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to create account"}) } return c.JSON(201, account) } func (h *Handlers) GetAccountHandler(c echo.Context) error { account := c.Get("account").(*db.Account) return c.JSON(200, account) } func (h *Handlers) CreatePaymentHandler(c echo.Context) error { account := c.Get("account").(*db.Account) var req struct { USDAmount float64 `json:"usd_amount"` } if err := c.Bind(&req); err != nil || req.USDAmount < 5.0 { return c.JSON(400, map[string]string{"error": "Minimum payment amount is $5.00 USD"}) } payment, err := h.accountService.CreatePayment(context.Background(), account.ID, req.USDAmount) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to create payment"}) } return c.JSON(201, payment) } func (h *Handlers) GetPaymentHandler(c echo.Context) error { paymentID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { return c.JSON(400, map[string]string{"error": "Invalid payment ID"}) } payment, err := h.accountService.CheckPaymentStatus(context.Background(), paymentID) if err != nil { return c.JSON(404, map[string]string{"error": "Payment not found"}) } return c.JSON(200, payment) } func (h *Handlers) GetPaymentsHandler(c echo.Context) error { account := c.Get("account").(*db.Account) payments, err := h.accountService.GetAccountPayments(context.Background(), account.ID) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to get payments"}) } return c.JSON(200, payments) } func (h *Handlers) ConfirmPaymentHandler(c echo.Context) error { paymentID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { return c.JSON(400, map[string]string{"error": "Invalid payment ID"}) } var req struct { TxHash string `json:"tx_hash"` } if err := c.Bind(&req); err != nil || req.TxHash == "" { return c.JSON(400, map[string]string{"error": "Transaction hash required"}) } if err := h.accountService.ConfirmPayment(context.Background(), paymentID, req.TxHash); err != nil { return c.JSON(500, map[string]string{"error": "Failed to confirm payment"}) } return c.JSON(200, map[string]string{"message": "Payment confirmed successfully"}) } func (h *Handlers) CreateBucketHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") if bucketName == "" { return c.JSON(400, map[string]string{"error": "Bucket name is required"}) } bucket, err := h.bucketService.CreateBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "duplicate key") { return c.JSON(409, map[string]string{"error": "Bucket name already exists"}) } return c.JSON(500, map[string]string{"error": "Failed to create bucket"}) } return c.JSON(201, bucket) } func (h *Handlers) ListBucketsHandler(c echo.Context) error { account := c.Get("account").(*db.Account) buckets, err := h.bucketService.GetUserBuckets(context.Background(), account.ID) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to list buckets"}) } return c.JSON(200, buckets) } func (h *Handlers) GetBucketHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } return c.JSON(200, bucket) } func (h *Handlers) DeleteBucketHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") if err := h.bucketService.DeleteBucket(context.Background(), bucketName, account.ID); err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to delete bucket"}) } return c.JSON(200, map[string]string{"message": "Bucket deleted successfully"}) } func (h *Handlers) PutObjectHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") objectKey := c.Param("*") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } contentType := c.Request().Header.Get("Content-Type") if contentType == "" { contentType = "application/octet-stream" } contentLength := c.Request().ContentLength if contentLength <= 0 { return c.JSON(400, map[string]string{"error": "Content-Length header required"}) } if err := h.accountService.CheckResourceLimits(context.Background(), account.ID, contentLength); err != nil { return c.JSON(413, map[string]string{"error": err.Error()}) } body := c.Request().Body defer body.Close() object, err := h.bucketService.UploadObject(context.Background(), bucket.ID, objectKey, contentLength, contentType, body) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to upload object"}) } if err := h.accountService.RecordUsage(context.Background(), account.ID, bucket.StorageUsedBytes+contentLength); err != nil { return c.JSON(500, map[string]string{"error": "Failed to record usage"}) } return c.JSON(201, object) } func (h *Handlers) GetObjectHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") objectKey := c.Param("*") file, err := h.bucketService.GetObjectFile(context.Background(), bucketName, objectKey, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Object not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get object"}) } defer file.Close() stat, err := file.Stat() if err != nil { return c.JSON(500, map[string]string{"error": "Failed to get file info"}) } c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10)) c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", objectKey)) return c.Stream(http.StatusOK, "application/octet-stream", file) } func (h *Handlers) ListObjectsHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } objects, err := h.bucketService.ListObjects(context.Background(), bucket.ID) if err != nil { return c.JSON(500, map[string]string{"error": "Failed to list objects"}) } return c.JSON(200, objects) } func (h *Handlers) DeleteObjectHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") objectKey := c.Param("*") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } if err := h.bucketService.DeleteObject(context.Background(), bucket.ID, objectKey); err != nil { return c.JSON(500, map[string]string{"error": "Failed to delete object"}) } return c.JSON(200, map[string]string{"message": "Object deleted successfully"}) } func (h *Handlers) SetBucketPolicyHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } body, err := io.ReadAll(c.Request().Body) if err != nil { return c.JSON(400, map[string]string{"error": "Invalid request body"}) } if err := h.bucketService.SetBucketPolicy(context.Background(), bucket.ID, string(body)); err != nil { if strings.Contains(err.Error(), "invalid policy") { return c.JSON(400, map[string]string{"error": err.Error()}) } return c.JSON(500, map[string]string{"error": "Failed to set bucket policy"}) } return c.JSON(200, map[string]string{"message": "Bucket policy set successfully"}) } func (h *Handlers) GetBucketPolicyHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } policyRecord, err := h.bucketService.GetBucketPolicy(context.Background(), bucket.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "No policy found for bucket"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket policy"}) } return c.JSON(200, policyRecord) } func (h *Handlers) DeleteBucketPolicyHandler(c echo.Context) error { account := c.Get("account").(*db.Account) bucketName := c.Param("bucket") bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, account.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { return c.JSON(404, map[string]string{"error": "Bucket not found"}) } return c.JSON(500, map[string]string{"error": "Failed to get bucket"}) } if err := h.bucketService.DeleteBucketPolicy(context.Background(), bucket.ID); err != nil { return c.JSON(500, map[string]string{"error": "Failed to delete bucket policy"}) } return c.JSON(200, map[string]string{"message": "Bucket policy deleted successfully"}) }