Skip to content

Commit da1cc7a

Browse files
authored
Feature/delete songs (#88)
* wip * wip 2 * beta test * update video_archive * temp fix * log error for not updating ui * wraaaaaa * no passing solid wall thing block * simba * s * sssssss * I saw this * fix test * Meh * play bug fix, ui fixes * block scroll content * wip ui
1 parent f5eee74 commit da1cc7a

File tree

23 files changed

+501
-116
lines changed

23 files changed

+501
-116
lines changed

MyMusicBoxApi/configuration/util.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package configuration
33
import (
44
"flag"
55
"fmt"
6+
"musicboxapi/logging"
67
"musicboxapi/models"
8+
"os"
79
)
810

911
var Config models.Config
@@ -24,5 +26,13 @@ func GetApiGroupUrl(version string) string {
2426
} else {
2527
return fmt.Sprintf("/api/%s", version)
2628
}
29+
}
30+
31+
func DeleteFile(path string) {
32+
err := os.Remove(path)
2733

34+
if err != nil {
35+
logging.ErrorStackTrace(err)
36+
logging.Error(fmt.Sprintf("Failed to delete file, path: %s", path))
37+
}
2838
}

MyMusicBoxApi/database/db.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func CreateDatabasConnectionPool() error {
4545
// Should create test for these?
4646
var _ ISongTable = (*SongTable)(nil)
4747
var _ IPlaylistTable = (*PlaylistTable)(nil)
48-
var _ IPlaylistsongTable = (*PlaylistsongTable)(nil)
48+
var _ IPlaylistSongTable = (*PlaylistsongTable)(nil)
4949
var _ ITasklogTable = (*TasklogTable)(nil)
5050
var _ IMigrationTable = (*MigrationTable)(nil)
5151

@@ -151,8 +151,8 @@ func (base *BaseTable) NonScalarQuery(query string, params ...any) (error error)
151151
return nil
152152
}
153153

154-
func (base *BaseTable) QueryRow(query string) *sql.Row {
155-
return base.DB.QueryRow(query)
154+
func (base *BaseTable) QueryRow(query string, params ...any) *sql.Row {
155+
return base.DB.QueryRow(query, params...)
156156
}
157157

158158
func (base *BaseTable) QueryRowsContex(ctx context.Context, query string, params ...any) (*sql.Rows, error) {

MyMusicBoxApi/database/playlistsongtable.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"musicboxapi/models"
88
)
99

10-
type IPlaylistsongTable interface {
10+
type IPlaylistSongTable interface {
1111
FetchPlaylistSongs(ctx context.Context, playlistId int, lastKnowPosition int) (songs []models.Song, error error)
1212
InsertPlaylistSong(playlistId int, songId int) (lastInsertedId int, error error)
1313
DeleteAllPlaylistSongs(playlistId int) (error error)
@@ -18,7 +18,7 @@ type PlaylistsongTable struct {
1818
BaseTable
1919
}
2020

21-
func NewPlaylistsongTableInstance() IPlaylistsongTable {
21+
func NewPlaylistsongTableInstance() IPlaylistSongTable {
2222
return &PlaylistsongTable{
2323
BaseTable: NewBaseTableInstance(),
2424
}

MyMusicBoxApi/database/playlisttable.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func NewPlaylistTableInstance() IPlaylistTable {
2424
}
2525

2626
func (table *PlaylistTable) FetchPlaylists(ctx context.Context, lastKnowPlaylistId int) (playlists []models.Playlist, error error) {
27-
query := "SELECT Id, Name, ThumbnailPath, Description, CreationDate FROM Playlist WHERE Id > $1 ORDER BY Id" // order by?
27+
query := "SELECT Id, Name, ThumbnailPath, Description, CreationDate, IsPublic FROM Playlist WHERE Id > $1 ORDER BY Id" // order by?
2828

2929
rows, err := table.QueryRowsContex(ctx, query, lastKnowPlaylistId)
3030

@@ -41,7 +41,7 @@ func (table *PlaylistTable) FetchPlaylists(ctx context.Context, lastKnowPlaylist
4141
playlists = make([]models.Playlist, 0)
4242

4343
for rows.Next() {
44-
scanError := rows.Scan(&playlist.Id, &playlist.Name, &playlist.ThumbnailPath, &playlist.Description, &playlist.CreationDate)
44+
scanError := rows.Scan(&playlist.Id, &playlist.Name, &playlist.ThumbnailPath, &playlist.Description, &playlist.CreationDate, &playlist.IsPublic)
4545

4646
if scanError != nil {
4747
logging.Error(fmt.Sprintf("Scan error: %s", scanError.Error()))
@@ -68,9 +68,9 @@ func (table *PlaylistTable) InsertPlaylist(playlist models.Playlist) (lastInsert
6868
}
6969

7070
func (table *PlaylistTable) DeletePlaylist(playlistId int) (error error) {
71-
query := `DELETE FROM Playlist WHERE Id = $1`
71+
query := `DELETE FROM Playlist WHERE Id = $1 AND IsPublic = $2` // Prevemts private playlists (like the default one) from being deleted for real
7272

73-
err := table.NonScalarQuery(query, playlistId)
73+
err := table.NonScalarQuery(query, playlistId, true)
7474

7575
if err != nil {
7676
logging.Error(fmt.Sprintf("Failed to delete playlist: %s", err.Error()))

MyMusicBoxApi/database/songtable.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
type ISongTable interface {
1111
InsertSong(song *models.Song) (err error)
1212
FetchSongs(ctx context.Context) (songs []models.Song, err error)
13+
FetchSongById(songId int) (song models.Song, err error)
14+
DeleteSongById(songId int) (err error)
1315
}
1416

1517
type SongTable struct {
@@ -47,6 +49,29 @@ func (table *SongTable) InsertSong(song *models.Song) (error error) {
4749
return err
4850
}
4951

52+
func (table *SongTable) FetchSongById(songId int) (song models.Song, err error) {
53+
54+
query := "SELECT Id, Name, Path, ThumbnailPath, Duration, SourceId, UpdatedAt, CreatedAt FROM Song WHERE Id = $1"
55+
56+
row := table.QueryRow(query, songId)
57+
58+
_err := row.Err()
59+
60+
if _err != nil {
61+
logging.ErrorStackTrace(_err)
62+
return models.Song{}, _err
63+
}
64+
65+
_err = row.Scan(&song.Id, &song.Name, &song.Path, &song.ThumbnailPath, &song.Duration, &song.SourceId, &song.UpdatedAt, &song.CreatedAt)
66+
67+
if _err != nil {
68+
logging.ErrorStackTrace(_err)
69+
return models.Song{}, _err
70+
}
71+
72+
return song, nil
73+
}
74+
5075
func (table *SongTable) FetchSongs(ctx context.Context) (songs []models.Song, error error) {
5176

5277
query := "SELECT Id, Name, Path, ThumbnailPath, Duration, SourceId, UpdatedAt, CreatedAt FROM Song" // order by?
@@ -77,3 +102,8 @@ func (table *SongTable) FetchSongs(ctx context.Context) (songs []models.Song, er
77102

78103
return songs, nil
79104
}
105+
106+
func (table *SongTable) DeleteSongById(songId int) (err error) {
107+
query := "DELETE FROM Song WHERE Id = $1"
108+
return table.NonScalarQuery(query, songId)
109+
}

MyMusicBoxApi/http/playlist.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ func (handler *PlaylistHandler) DeletePlaylist(ctx *gin.Context) {
110110
return
111111
}
112112

113+
if DefaultPlaylistId == id {
114+
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse("Funky music... "))
115+
return
116+
}
117+
113118
err = handler.PlaylistTable.DeletePlaylist(id)
114119

115120
// TODO delete background image if its not the default image for it

MyMusicBoxApi/http/playlist_test.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,7 @@ func TestDeletePlaylistPlaylistId(t *testing.T) {
265265

266266
recorder := httptest.NewRecorder()
267267

268-
// Unable to parse to int, will throw error
269-
_route := "/playlist/1"
268+
_route := "/playlist/2"
270269

271270
req, _ := http.NewRequest("DELETE", _route, nil)
272271

@@ -276,3 +275,33 @@ func TestDeletePlaylistPlaylistId(t *testing.T) {
276275
// Assert
277276
assert.Equal(t, http.StatusOK, recorder.Code)
278277
}
278+
279+
func TestDeletePlaylistPlaylistIdDefaultPlaylist(t *testing.T) {
280+
// Arrange
281+
route := "/playlist/:playlistId"
282+
router := SetupTestRouter()
283+
284+
mockTable := &mockPlaylistTable{
285+
deletePlaylist: func(playlistId int) (error error) {
286+
return nil
287+
},
288+
}
289+
290+
playlistHandler := PlaylistHandler{
291+
PlaylistTable: mockTable,
292+
}
293+
294+
router.DELETE(route, playlistHandler.DeletePlaylist)
295+
296+
recorder := httptest.NewRecorder()
297+
298+
_route := "/playlist/1"
299+
300+
req, _ := http.NewRequest("DELETE", _route, nil)
301+
302+
// Act
303+
router.ServeHTTP(recorder, req)
304+
305+
// Assert
306+
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
307+
}

MyMusicBoxApi/http/playlistsong.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
package http
22

33
import (
4+
"bufio"
45
"fmt"
6+
"musicboxapi/configuration"
57
"musicboxapi/database"
68
"musicboxapi/models"
79
"net/http"
10+
"os"
811
"strconv"
12+
"strings"
913

1014
"github.com/gin-gonic/gin"
1115
)
1216

1317
type PlaylistSongHandler struct {
14-
PlaylistsongTable database.IPlaylistsongTable
18+
PlaylistsongTable database.IPlaylistSongTable
1519
}
1620

21+
const DefaultPlaylistId = 1
22+
1723
// @Produce json
1824
// @Param playlistId path int true "Id of playlist"
1925
// @Param lastKnowSongPosition path int false "Last song that is know by the client, pass this in to only get the latest songs"
@@ -99,5 +105,74 @@ func (handler *PlaylistSongHandler) DeletePlaylistSong(ctx *gin.Context) {
99105
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
100106
return
101107
}
108+
109+
// Thumbnail and .opus file will be deleted only if you delete a song via the main playlist containing all the songs
110+
if playlistId == DefaultPlaylistId {
111+
112+
songTable := database.NewSongTableInstance()
113+
song, err := songTable.FetchSongById(songId)
114+
115+
if err != nil {
116+
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
117+
return
118+
}
119+
120+
// Delete actual song file
121+
audioFilePath := song.Path
122+
configuration.DeleteFile(audioFilePath)
123+
124+
// Delete actual thumbnail file
125+
thumbnail := song.ThumbnailPath
126+
thumbnailPath := fmt.Sprintf("%s/images/%s", configuration.Config.SourceFolder, thumbnail)
127+
configuration.DeleteFile(thumbnailPath)
128+
129+
// Delete from database
130+
songTable.DeleteSongById(song.Id)
131+
132+
// Delete from video_archive
133+
strSplit := strings.Split(song.Path, ".")
134+
135+
filenameWithOutExtension := strSplit[0]
136+
strSplit = strings.Split(filenameWithOutExtension, "/")
137+
138+
filenameWithOutExtension = strSplit[1]
139+
140+
filePath := fmt.Sprintf("%s/%s", configuration.Config.SourceFolder, "video_archive")
141+
142+
// Read the file
143+
file, err := os.Open(filePath)
144+
if err != nil {
145+
fmt.Println("Error opening file:", err)
146+
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
147+
return
148+
}
149+
defer file.Close()
150+
151+
var lines []string
152+
scanner := bufio.NewScanner(file)
153+
for scanner.Scan() {
154+
line := scanner.Text()
155+
// Keep the line only if it’s not the target
156+
if strings.TrimSpace(line) != fmt.Sprintf("youtube %s", filenameWithOutExtension) {
157+
lines = append(lines, line)
158+
}
159+
}
160+
161+
if err := scanner.Err(); err != nil {
162+
fmt.Println("Error reading file:", err)
163+
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
164+
return
165+
}
166+
167+
// Write the updated content back to the file
168+
output := strings.Join(lines, "\n")
169+
err = os.WriteFile(filePath, []byte(output+"\n"), 0644)
170+
if err != nil {
171+
fmt.Println("Error writing file:", err)
172+
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
173+
return
174+
}
175+
}
176+
102177
ctx.Status(http.StatusOK)
103178
}

MyMusicBoxApi/http/playlistsong_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
type mockPlaylistSongTable struct {
16-
database.IPlaylistsongTable
16+
database.IPlaylistSongTable
1717
fetchPlaylistSongs func(ctx context.Context, playlistId int, lastKnowPosition int) (songs []models.Song, error error)
1818
insertPlaylistSong func(playlistId int, songId int) (lastInsertedId int, error error)
1919
deleteAllPlaylistSongs func(playlistId int) (error error) // TODO

MyMusicBoxApi/models/http.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ type ApiResponseModel struct {
1919
}
2020

2121
func ErrorResponse(data any) ApiResponseModel {
22+
2223
return ApiResponseModel{
2324
Data: data,
2425
Message: "An error occurred",
2526
}
2627
}
28+
2729
func OkResponse(data any, message string) ApiResponseModel {
2830
return ApiResponseModel{
2931
Data: data,

0 commit comments

Comments
 (0)