344 lines
10 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"git.keircn.com/keiran/termcloud/internal/db"
"github.com/labstack/echo/v4"
)
type Handlers struct {
bucketService *db.BucketService
}
func NewHandlers(bucketService *db.BucketService) *Handlers {
return &Handlers{bucketService: bucketService}
}
func (h *Handlers) RootHandler(c echo.Context) error {
return c.JSON(200, map[string]string{
"status": "😺",
"docs": "https://illfillthisoutlater.com",
})
}
func (h *Handlers) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
apiKey := c.Request().Header.Get("X-API-Key")
if apiKey == "" {
return c.JSON(401, map[string]string{"error": "API key required"})
}
user, err := h.bucketService.GetUserByAPIKey(context.Background(), apiKey)
if err != nil {
return c.JSON(401, map[string]string{"error": "Invalid API key"})
}
c.Set("user", user)
return next(c)
}
}
func (h *Handlers) CreateBucketHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
var req struct {
Name string `json:"name"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request body"})
}
if req.Name == "" {
return c.JSON(400, map[string]string{"error": "Bucket name is required"})
}
bucket, err := h.bucketService.CreateBucket(context.Background(), req.Name, user.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 {
user := c.Get("user").(*db.User)
buckets, err := h.bucketService.GetUserBuckets(context.Background(), user.ID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to list buckets"})
}
return c.JSON(200, map[string]any{
"buckets": buckets,
})
}
func (h *Handlers) DeleteBucketHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
if err := h.bucketService.DeleteBucket(context.Background(), bucketName, user.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) UploadObjectHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
objectKey := c.Param("*")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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"})
}
file, err := c.FormFile("file")
if err != nil {
return c.JSON(400, map[string]string{"error": "Failed to retrieve file"})
}
src, err := file.Open()
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to open file"})
}
defer src.Close()
contentType := file.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/octet-stream"
}
object, err := h.bucketService.UploadObject(context.Background(), bucket.ID, objectKey, file.Size, contentType, src)
if err != nil {
if strings.Contains(err.Error(), "storage limit exceeded") {
return c.JSON(413, map[string]string{"error": "Storage limit exceeded"})
}
return c.JSON(500, map[string]string{"error": "Failed to upload object"})
}
return c.JSON(201, object)
}
func (h *Handlers) ListObjectsHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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, map[string]any{
"objects": objects,
})
}
func (h *Handlers) GetObjectHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
objectKey := c.Param("*")
file, err := h.bucketService.GetObjectFile(context.Background(), bucketName, objectKey, user.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) DeleteObjectHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
objectKey := c.Param("*")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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) GetUserInfoHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
buckets, err := h.bucketService.GetUserBuckets(context.Background(), user.ID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to get user buckets"})
}
var totalUsage int64
for _, bucket := range buckets {
totalUsage += bucket.StorageUsedBytes
}
return c.JSON(200, map[string]any{
"user": user,
"totalUsage": totalUsage,
"bucketCount": len(buckets),
})
}
func (h *Handlers) SetBucketPolicyHandler(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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"})
}
var req struct {
Policy string `json:"policy"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request body"})
}
if err := h.bucketService.SetBucketPolicy(context.Background(), bucket.ID, req.Policy); 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 {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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 {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.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"})
}
func (h *Handlers) PolicyEnforcementMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
user := c.Get("user").(*db.User)
bucketName := c.Param("bucket")
if bucketName == "" {
return next(c)
}
bucket, err := h.bucketService.GetBucket(context.Background(), bucketName, user.ID)
if err != nil {
return next(c)
}
action := mapHTTPMethodToAction(c.Request().Method, c.Path())
resource := fmt.Sprintf("arn:termcloud:s3:::%s/*", bucketName)
principal := user.Username
allowed, err := h.bucketService.EvaluatePolicy(context.Background(), bucket.ID, action, resource, principal)
if err != nil {
return c.JSON(500, map[string]string{"error": "Policy evaluation failed"})
}
if !allowed {
return c.JSON(403, map[string]string{"error": "Access denied by bucket policy"})
}
return next(c)
}
}
func mapHTTPMethodToAction(method, path string) string {
switch method {
case "GET":
if strings.Contains(path, "/objects") && !strings.HasSuffix(path, "/objects") {
return "termcloud:GetObject"
}
return "termcloud:ListObjects"
case "PUT":
return "termcloud:PutObject"
case "DELETE":
if strings.Contains(path, "/objects") {
return "termcloud:DeleteObject"
}
return "termcloud:DeleteBucket"
default:
return "termcloud:GetBucket"
}
}