From b61bd2f8d66f75ffc544889e664711c9b207011e Mon Sep 17 00:00:00 2001 From: wisplite Date: Sun, 23 Nov 2025 00:27:57 -0600 Subject: [PATCH] add file upload logic (it's kinda broken rn) --- .gitignore | 3 +- backend/internal/db/db.go | 1 + backend/internal/routes/media.go | 39 ++++++++++++++++++ backend/internal/routes/routes.go | 1 + backend/internal/services/media.go | 40 +++++++++++++++++++ .../gallery/components/MediaUploadModal.jsx | 38 +++++++++--------- 6 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 backend/internal/routes/media.go create mode 100644 backend/internal/services/media.go diff --git a/.gitignore b/.gitignore index 146b9c9..8300337 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -backend/raster.db \ No newline at end of file +backend/raster.db +backend/media \ No newline at end of file diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index 82ff344..e38a6f6 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -23,6 +23,7 @@ func Init() bool { &models.User{}, &models.AccessToken{}, &models.UserAlbumAccess{}, + &models.Media{}, ) if err != nil { log.Fatal("failed to migrate database: ", err) diff --git a/backend/internal/routes/media.go b/backend/internal/routes/media.go new file mode 100644 index 0000000..619c9ef --- /dev/null +++ b/backend/internal/routes/media.go @@ -0,0 +1,39 @@ +package routes + +import ( + "net/http" + "os" + "path/filepath" + + "github.com/gin-gonic/gin" + "github.com/wisplite/raster/internal/services" +) + +func RegisterMediaRoutes(rg *gin.RouterGroup) { + media := rg.Group("/media") + media.POST("/uploadMedia", func(c *gin.Context) { + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + accessToken := c.GetHeader("Authorization") + albumID := c.PostForm("albumId") + media, err := services.UploadMedia(file, albumID, accessToken) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if err := os.MkdirAll(filepath.Dir(media.Path), 0755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create directory"}) + return + } + + if err := c.SaveUploadedFile(file, media.Path); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save file"}) + return + } + c.JSON(http.StatusOK, gin.H{"media": media}) + }) +} diff --git a/backend/internal/routes/routes.go b/backend/internal/routes/routes.go index d45b002..18faa16 100644 --- a/backend/internal/routes/routes.go +++ b/backend/internal/routes/routes.go @@ -6,4 +6,5 @@ func RegisterRoutes(r *gin.Engine) { rg := r.Group("/api") RegisterAlbumRoutes(rg) RegisterUserRoutes(rg) + RegisterMediaRoutes(rg) } diff --git a/backend/internal/services/media.go b/backend/internal/services/media.go new file mode 100644 index 0000000..9fbd764 --- /dev/null +++ b/backend/internal/services/media.go @@ -0,0 +1,40 @@ +package services + +import ( + "fmt" + "github.com/google/uuid" + "github.com/wisplite/raster/internal/db" + "github.com/wisplite/raster/internal/models" + "mime/multipart" +) + +func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string) (models.Media, error) { + userID, err := ValidateAccessToken(accessToken) + if err != nil { + return models.Media{}, err + } + accessLevel, err := CheckUserAlbumAccess(userID, albumID) + if err != nil { + return models.Media{}, err + } + if accessLevel < 1 { + return models.Media{}, fmt.Errorf("user does not have permission to upload media to this album") + } + albumPath := albumID + if albumID == "" { + albumPath = "root" + } + mediaID := uuid.New().String() + mediaPath := fmt.Sprintf("media/%s/%s.%s", albumPath, mediaID, file.Filename) + media := models.Media{ + ID: mediaID, + AlbumID: albumID, + Path: mediaPath, + Type: file.Header.Get("Content-Type"), + } + result := db.GetDB().Create(&media) + if result.Error != nil { + return models.Media{}, result.Error + } + return media, nil +} diff --git a/frontend/src/gallery/components/MediaUploadModal.jsx b/frontend/src/gallery/components/MediaUploadModal.jsx index 53b9bd3..3e7e87c 100644 --- a/frontend/src/gallery/components/MediaUploadModal.jsx +++ b/frontend/src/gallery/components/MediaUploadModal.jsx @@ -2,12 +2,13 @@ import Modal from '../../components/Modal' import { useAccount } from '../../contexts/useAccount' import { useState, useRef } from 'react' import { X, Upload, FileImage, FileVideo, Trash2 } from 'lucide-react' - +import { useNotifier } from '../../contexts/useNotifier' +import { getServerUrl } from '../../hooks/getConstants' export default function MediaUploadModal({ open, onOpenChange, trigger, albumName, albumId }) { const { getAccessToken } = useAccount() const [files, setFiles] = useState([]) const fileInputRef = useRef(null) - + const { showError, showSuccess } = useNotifier() const handleFileSelect = (e) => { if (e.target.files) { const newFiles = Array.from(e.target.files).map(file => ({ @@ -38,24 +39,23 @@ export default function MediaUploadModal({ open, onOpenChange, trigger, albumNam // formData.append('albumId', albumId) // await fetch('/api/upload', { method: 'POST', body: formData, ... }) - // Simulation: - return new Promise((resolve) => { - let progress = 0 - const interval = setInterval(() => { - progress += 5 - setFiles(prev => prev.map(f => { - if (f.id === fileWrapper.id) { - if (progress >= 100) { - clearInterval(interval) - resolve() - return { ...f, progress: 100, status: 'completed' } - } - return { ...f, progress, status: 'uploading' } - } - return f - })) - }, 100) + const formData = new FormData() + formData.append('file', fileWrapper.file) + formData.append('albumId', albumId) + const response = await fetch(`${getServerUrl()}/api/media/uploadMedia`, { + method: 'POST', + body: formData, + headers: { + 'Authorization': getAccessToken(), + }, }) + const data = await response.json() + if (data.error) { + showError(data.error) + } else { + setFiles(prev => prev.map(f => f.id === fileWrapper.id ? { ...f, status: 'completed' } : f)) + showSuccess('Media uploaded successfully') + } } // Start uploads