284 lines
7.2 KiB
Go
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
|
|
}
|