package controller import ( "database/sql" "fmt" "github.com/gin-gonic/gin" "image/jpeg" "net/http" "path/filepath" "photodisk/internal/albums" "photodisk/internal/auth" "photodisk/internal/config" "photodisk/internal/fs" "photodisk/internal/watermark" "time" ) // Login type LoginRequest struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func Login(c *gin.Context) { var req LoginRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, err := auth.Login(req.Username, req.Password) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } expiresAt := time.Now().Add(config.Get().SessionTtl) sessionId, err := auth.CreateSession(user.Id, expiresAt) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } cookie := &http.Cookie{ Name: "session_id", Value: sessionId, Expires: expiresAt, Path: "/", } http.SetCookie(c.Writer, cookie) c.JSON(http.StatusOK, gin.H{"session": sessionId}) } // Create type CreateAlbumRequest struct { Name string `form:"name" binding:"required"` Password string `form:"password"` } type CreateAlbumResponse struct { Id string `json:"id"` } func CreateAlbum(c *gin.Context) { var req CreateAlbumRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } id := albums.GenerateAlbumId() if err := albums.CreateAlbum(albums.Album{ ID: id, Name: req.Name, Password: req.Password, }); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, CreateAlbumResponse{Id: id}) } // Delete type DeleteAlbumRequest struct { Id string `form:"id" binding:"required"` } type DeleteAlbumResponse struct { } func DeleteAlbum(c *gin.Context) { var req DeleteAlbumRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := albums.DeleteAlbum(req.Id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, DeleteAlbumResponse{}) } // List type ListAlbumsRequest struct { } type ListAlbumsResponse struct { Albums []albums.Album `json:"albums"` } func ListAlbums(c *gin.Context) { var req ListAlbumsRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } list, err := albums.ListAlbums() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, ListAlbumsResponse{Albums: list}) } // List Images type ListImagesRequest struct { Password string `form:"password"` } type ListImagesResponse struct { Images []string `json:"images"` } func ListImages(c *gin.Context) { id := c.Param("id") var req ListImagesRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } album, err := albums.GetAlbum(id, req.Password) if err != nil { c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return } files, err := fs.ListFiles(album.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var images []string for _, file := range files { suffix := "" if filepath.Ext(file) != ".mp4" { suffix = "?width=200" } images = append(images, fmt.Sprintf("/albums/%s/%s%s", album.ID, file, suffix)) } c.JSON(http.StatusOK, ListImagesResponse{Images: images}) } // Get Album type GetAlbumRequest struct { Id string `form:"id" binding:"required"` Password string `form:"password"` } type GetAlbumResponse struct { Album albums.Album `json:"album"` } func GetAlbum(c *gin.Context) { var req GetAlbumRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } album, err := albums.GetAlbum(req.Id, req.Password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, GetAlbumResponse{Album: album}) } // Update Album type UpdateAlbumRequest struct { Id string `form:"id" binding:"required"` Name string `form:"name" binding:"required"` Password string `form:"password"` IsActive bool `form:"is_active"` } type UpdateAlbumResponse struct { Album albums.Album `json:"album"` } func UpdateAlbum(c *gin.Context) { var req UpdateAlbumRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } album, err := albums.GetAlbum(req.Id, req.Password) if err != nil { c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return } album.Name = req.Name album.IsActive = req.IsActive if err := albums.UpdateAlbum(album); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, UpdateAlbumResponse{Album: album}) } // Serve Image type ServeImageRequest struct { Width int `form:"width"` } func ServeImage(c *gin.Context) { albumID := c.Param("id") fileName := c.Param("image") if albumID == "" || fileName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "album and file query parameters are required"}) return } var req ServeImageRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // just to check access album, err := albums.GetAlbum(albumID, "") if err != nil { c.JSON(http.StatusForbidden, gin.H{"error": err}) return } path, exists := fs.FileExists(album.ID, fileName) if exists { /* // Cache control data := []byte(time.Now().String()) etag := fmt.Sprintf("%x", md5.Sum(data)) c.Header("Cache-Control", "public, max-age=3600") c.Header("ETag", etag) if match := c.GetHeader("If-None-Match"); match != "" { if strings.Contains(match, etag) { c.Status(http.StatusNotModified) return } } */ if album.Watermarked && fs.IsImageFile(fileName) { watermarkedImg, err := watermark.AddWatermark(path, config.Get().Watermark) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Header("Content-Type", "image/jpeg") // Resize image if req.Width > 0 { watermarkedImg = watermark.Resize(watermarkedImg, req.Width) } jpeg.Encode(c.Writer, watermarkedImg, nil) return } c.File(path) return } c.JSON(http.StatusNotFound, gin.H{"error": "File not found"}) } // Upload Image func UploadImage(c *gin.Context) { albumID := c.Param("id") // Parse the multipart form err := c.Request.ParseMultipartForm(10 << 20) // Max size: 10MB if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Check if the album exists _, err = albums.GetAlbum(albumID, "") // FIXME: add password support if err != nil { if err == sql.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{"error": "Album not found"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } files := c.Request.MultipartForm.File["files[]"] for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) file.Close() return } if err := fs.CreateFile(albumID, fileHeader.Filename, file); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) file.Close() return } file.Close() } c.JSON(http.StatusOK, gin.H{"message": "Images uploaded successfully"}) }