354 lines
7.6 KiB
Go
354 lines
7.6 KiB
Go
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"})
|
|
}
|