Files

284 lines
7.2 KiB
Go

package handlers
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/toyffee/party-mix/internal/models"
"gorm.io/gorm"
)
func parseTags(s string) []string {
if s == "" {
return []string{}
}
result := []string{}
for _, t := range strings.Split(s, ",") {
if t = strings.TrimSpace(t); t != "" {
result = append(result, t)
}
}
return result
}
func joinTags(tags []string) string {
cleaned := make([]string, 0, len(tags))
for _, t := range tags {
if t = strings.TrimSpace(t); t != "" {
cleaned = append(cleaned, t)
}
}
return strings.Join(cleaned, ",")
}
type playlistResp struct {
models.Playlist
Tags []string `json:"tags"`
}
type pubPlaylistResp struct {
models.Playlist
Tags []string `json:"tags"`
Username string `json:"username"`
}
func toResp(p models.Playlist) playlistResp {
return playlistResp{Playlist: p, Tags: parseTags(p.Tags)}
}
func GetPublicPlaylists(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var playlists []models.Playlist
db.Preload("Tracks", func(tx *gorm.DB) *gorm.DB {
return tx.Order("position asc")
}).Where("is_public = ?", true).Order("created_at desc").Find(&playlists)
type UserRow struct {
ID string
Username string
}
userIDs := make([]string, 0, len(playlists))
for _, p := range playlists {
userIDs = append(userIDs, p.UserID)
}
var users []UserRow
db.Model(&models.User{}).Select("id, username").Where("id IN ?", userIDs).Find(&users)
userMap := make(map[string]string, len(users))
for _, u := range users {
userMap[u.ID] = u.Username
}
resp := make([]pubPlaylistResp, 0, len(playlists))
for _, p := range playlists {
resp = append(resp, pubPlaylistResp{
Playlist: p,
Tags: parseTags(p.Tags),
Username: userMap[p.UserID],
})
}
c.JSON(http.StatusOK, resp)
}
}
func GetPlaylists(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID := currentUserID(c)
var playlists []models.Playlist
db.Preload("Tracks", func(tx *gorm.DB) *gorm.DB {
return tx.Order("position asc")
}).Where("user_id = ?", userID).Order("created_at desc").Find(&playlists)
resp := make([]playlistResp, 0, len(playlists))
for _, p := range playlists {
resp = append(resp, toResp(p))
}
c.JSON(http.StatusOK, resp)
}
}
type playlistTrackInput struct {
Title string `json:"title"`
Position int `json:"position"`
}
type playlistReq struct {
Name string `json:"name" binding:"required"`
Tracks []playlistTrackInput `json:"tracks"`
IsPublic bool `json:"is_public"`
Tags []string `json:"tags"`
}
func CreatePlaylist(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req playlistReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
playlist := models.Playlist{
ID: uuid.NewString(),
UserID: currentUserID(c),
Name: req.Name,
IsPublic: req.IsPublic,
Tags: joinTags(req.Tags),
CreatedAt: time.Now(),
}
tracks := buildTracks(playlist.ID, req.Tracks)
if err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&playlist).Error; err != nil {
return err
}
if len(tracks) > 0 {
return tx.Create(&tracks).Error
}
return nil
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create playlist"})
return
}
playlist.Tracks = tracks
c.JSON(http.StatusCreated, toResp(playlist))
}
}
func GetPlaylist(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID := currentUserID(c)
var playlist models.Playlist
if err := db.Preload("Tracks", func(tx *gorm.DB) *gorm.DB {
return tx.Order("position asc")
}).First(&playlist, "id = ?", c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
return
}
if playlist.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
c.JSON(http.StatusOK, toResp(playlist))
}
}
func UpdatePlaylist(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID := currentUserID(c)
var playlist models.Playlist
if err := db.First(&playlist, "id = ?", c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
return
}
if playlist.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
var req playlistReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
playlist.Name = req.Name
playlist.IsPublic = req.IsPublic
playlist.Tags = joinTags(req.Tags)
newTracks := buildTracks(playlist.ID, req.Tracks)
if err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(&playlist).Error; err != nil {
return err
}
if err := tx.Where("playlist_id = ?", playlist.ID).Delete(&models.PlaylistTrack{}).Error; err != nil {
return err
}
if len(newTracks) > 0 {
return tx.Create(&newTracks).Error
}
return nil
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not update playlist"})
return
}
playlist.Tracks = newTracks
c.JSON(http.StatusOK, toResp(playlist))
}
}
func AddTrackToPlaylist(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID := currentUserID(c)
var playlist models.Playlist
if err := db.Preload("Tracks").First(&playlist, "id = ?", c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
return
}
if playlist.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
var req struct {
Title string `json:"title" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
track := models.PlaylistTrack{
ID: uuid.NewString(),
PlaylistID: playlist.ID,
Title: req.Title,
Position: len(playlist.Tracks),
CreatedAt: time.Now(),
}
if err := db.Create(&track).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not add track"})
return
}
c.JSON(http.StatusCreated, track)
}
}
func DeletePlaylist(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
userID := currentUserID(c)
var playlist models.Playlist
if err := db.First(&playlist, "id = ?", c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
return
}
if playlist.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
db.Where("playlist_id = ?", playlist.ID).Delete(&models.PlaylistTrack{})
db.Delete(&playlist)
c.Status(http.StatusNoContent)
}
}
func buildTracks(playlistID string, inputs []playlistTrackInput) []models.PlaylistTrack {
tracks := make([]models.PlaylistTrack, 0, len(inputs))
for i, t := range inputs {
if t.Title == "" {
continue
}
pos := t.Position
if pos == 0 {
pos = i
}
tracks = append(tracks, models.PlaylistTrack{
ID: uuid.NewString(),
PlaylistID: playlistID,
Title: t.Title,
Position: pos,
CreatedAt: time.Now(),
})
}
return tracks
}