From 2b7268b3f617e7dbb37c9af62f469ad92645a202 Mon Sep 17 00:00:00 2001 From: wisplite Date: Sun, 23 Nov 2025 02:27:09 -0600 Subject: [PATCH] Image loading and auth --- backend/internal/routes/media.go | 38 +++++++++++ backend/internal/services/media.go | 29 +++++++++ frontend/src/components/AuthImage.jsx | 65 +++++++++++++++++++ frontend/src/gallery/components/MediaList.jsx | 31 +++++++-- 4 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/AuthImage.jsx diff --git a/backend/internal/routes/media.go b/backend/internal/routes/media.go index 619c9ef..78c6ed1 100644 --- a/backend/internal/routes/media.go +++ b/backend/internal/routes/media.go @@ -36,4 +36,42 @@ func RegisterMediaRoutes(rg *gin.RouterGroup) { } c.JSON(http.StatusOK, gin.H{"media": media}) }) + media.GET("/getAllMediaInAlbum", func(c *gin.Context) { + accessToken := c.GetHeader("Authorization") + albumID := c.Query("albumId") + media, err := services.GetAllMediaInAlbum(albumID, accessToken) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"media": media}) + }) + media.GET("/:albumId/:mediaId", func(c *gin.Context) { + albumID := c.Param("albumId") + mediaID := c.Param("mediaId") + if albumID == "root" { + albumID = "" + } + accessToken := c.GetHeader("Authorization") + userID, err := services.ValidateAccessToken(accessToken) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + accessLevel, err := services.CheckUserAlbumAccess(userID, albumID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if accessLevel < 0 { + c.JSON(http.StatusForbidden, gin.H{"error": "user does not have permission to view media in this album"}) + return + } + mediaData, err := services.GetMedia(albumID, mediaID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.File(mediaData.Path) + }) } diff --git a/backend/internal/services/media.go b/backend/internal/services/media.go index 9fbd764..ff0859f 100644 --- a/backend/internal/services/media.go +++ b/backend/internal/services/media.go @@ -38,3 +38,32 @@ func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string) } return media, nil } + +func GetAllMediaInAlbum(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 < 0 { + return []models.Media{}, fmt.Errorf("user does not have permission to view media in this album") + } + media := []models.Media{} + result := db.GetDB().Where("album_id = ?", albumID).Find(&media) + if result.Error != nil { + return []models.Media{}, result.Error + } + return media, nil +} + +func GetMedia(albumID string, mediaID string) (models.Media, error) { + media := models.Media{} + result := db.GetDB().First(&media, "album_id = ? AND id = ?", albumID, mediaID) + if result.Error != nil { + return models.Media{}, result.Error + } + return media, nil +} diff --git a/frontend/src/components/AuthImage.jsx b/frontend/src/components/AuthImage.jsx new file mode 100644 index 0000000..211a727 --- /dev/null +++ b/frontend/src/components/AuthImage.jsx @@ -0,0 +1,65 @@ +import { useState, useEffect } from 'react' + +export default function AuthImage({ src, token, alt, className, ...props }) { + const [imageSrc, setImageSrc] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(false) + + useEffect(() => { + let objectUrl = null + let active = true + + const fetchImage = async () => { + setLoading(true) + setError(false) + try { + const response = await fetch(src, { + headers: { + 'Authorization': token + } + }) + + if (!response.ok) { + throw new Error('Failed to load image') + } + + const blob = await response.blob() + if (active) { + objectUrl = URL.createObjectURL(blob) + setImageSrc(objectUrl) + setLoading(false) + } + } catch (err) { + if (active) { + console.error("Error loading image:", err) + setError(true) + setLoading(false) + } + } + } + + if (src && token) { + fetchImage() + } else { + setLoading(false) + } + + return () => { + active = false + if (objectUrl) { + URL.revokeObjectURL(objectUrl) + } + } + }, [src, token]) + + if (loading) { + return
+ } + + if (error || !imageSrc) { + return
Error
+ } + + return {alt} +} + diff --git a/frontend/src/gallery/components/MediaList.jsx b/frontend/src/gallery/components/MediaList.jsx index 702136d..28ab796 100644 --- a/frontend/src/gallery/components/MediaList.jsx +++ b/frontend/src/gallery/components/MediaList.jsx @@ -1,5 +1,6 @@ import { Upload } from 'lucide-react' import MediaUploadModal from './MediaUploadModal' +import AuthImage from '../../components/AuthImage' import { useState, useEffect } from 'react' import { getServerUrl } from '../../hooks/getConstants' import { useAccount } from '../../contexts/useAccount' @@ -14,12 +15,23 @@ export default function MediaList({ albumId, albumName }) { useEffect(() => { let ignore = false; const getMedia = async () => { - if (!albumId) return - // TODO: Implement media fetching from API // const response = await fetch(...) + const response = await fetch(`${getServerUrl()}/api/media/getAllMediaInAlbum?albumId=${albumId}`, { + method: 'GET', + headers: { + 'Authorization': getAccessToken(), + }, + }) + const data = await response.json() + if (ignore) return + if (data.error) { + showError(data.error) + } else { + console.log("data.media", data.media) + setMedia(data.media) + } } - getMedia() return () => { ignore = true; } }, [albumId]) @@ -31,12 +43,21 @@ export default function MediaList({ albumId, albumName }) { setOpen(true)} />
- {/* Media Grid Placeholder */} + {/* Media Grid */}
{media.length === 0 && (

No media in this album

)} - {/* Render media items here */} + {media.map((media) => ( +
+ +
+ ))}