2025-08-07 19:10:05 +01:00

367 lines
12 KiB
Go

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"})
}