Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions MyMusicBoxApi/configuration/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package configuration
import (
"flag"
"fmt"
"musicboxapi/logging"
"musicboxapi/models"
"os"
)

var Config models.Config
Expand All @@ -24,5 +26,13 @@ func GetApiGroupUrl(version string) string {
} else {
return fmt.Sprintf("/api/%s", version)
}
}

func DeleteFile(path string) {
err := os.Remove(path)

if err != nil {
logging.ErrorStackTrace(err)
logging.Error(fmt.Sprintf("Failed to delete file, path: %s", path))
}
}
6 changes: 3 additions & 3 deletions MyMusicBoxApi/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func CreateDatabasConnectionPool() error {
// Should create test for these?
var _ ISongTable = (*SongTable)(nil)
var _ IPlaylistTable = (*PlaylistTable)(nil)
var _ IPlaylistsongTable = (*PlaylistsongTable)(nil)
var _ IPlaylistSongTable = (*PlaylistsongTable)(nil)
var _ ITasklogTable = (*TasklogTable)(nil)
var _ IMigrationTable = (*MigrationTable)(nil)

Expand Down Expand Up @@ -151,8 +151,8 @@ func (base *BaseTable) NonScalarQuery(query string, params ...any) (error error)
return nil
}

func (base *BaseTable) QueryRow(query string) *sql.Row {
return base.DB.QueryRow(query)
func (base *BaseTable) QueryRow(query string, params ...any) *sql.Row {
return base.DB.QueryRow(query, params...)
}

func (base *BaseTable) QueryRowsContex(ctx context.Context, query string, params ...any) (*sql.Rows, error) {
Expand Down
4 changes: 2 additions & 2 deletions MyMusicBoxApi/database/playlistsongtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"musicboxapi/models"
)

type IPlaylistsongTable interface {
type IPlaylistSongTable interface {
FetchPlaylistSongs(ctx context.Context, playlistId int, lastKnowPosition int) (songs []models.Song, error error)
InsertPlaylistSong(playlistId int, songId int) (lastInsertedId int, error error)
DeleteAllPlaylistSongs(playlistId int) (error error)
Expand All @@ -18,7 +18,7 @@ type PlaylistsongTable struct {
BaseTable
}

func NewPlaylistsongTableInstance() IPlaylistsongTable {
func NewPlaylistsongTableInstance() IPlaylistSongTable {
return &PlaylistsongTable{
BaseTable: NewBaseTableInstance(),
}
Expand Down
8 changes: 4 additions & 4 deletions MyMusicBoxApi/database/playlisttable.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewPlaylistTableInstance() IPlaylistTable {
}

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

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

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

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

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

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

err := table.NonScalarQuery(query, playlistId)
err := table.NonScalarQuery(query, playlistId, true)

if err != nil {
logging.Error(fmt.Sprintf("Failed to delete playlist: %s", err.Error()))
Expand Down
30 changes: 30 additions & 0 deletions MyMusicBoxApi/database/songtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
type ISongTable interface {
InsertSong(song *models.Song) (err error)
FetchSongs(ctx context.Context) (songs []models.Song, err error)
FetchSongById(songId int) (song models.Song, err error)
DeleteSongById(songId int) (err error)
}

type SongTable struct {
Expand Down Expand Up @@ -47,6 +49,29 @@ func (table *SongTable) InsertSong(song *models.Song) (error error) {
return err
}

func (table *SongTable) FetchSongById(songId int) (song models.Song, err error) {

query := "SELECT Id, Name, Path, ThumbnailPath, Duration, SourceId, UpdatedAt, CreatedAt FROM Song WHERE Id = $1"

row := table.QueryRow(query, songId)

_err := row.Err()

if _err != nil {
logging.ErrorStackTrace(_err)
return models.Song{}, _err
}

_err = row.Scan(&song.Id, &song.Name, &song.Path, &song.ThumbnailPath, &song.Duration, &song.SourceId, &song.UpdatedAt, &song.CreatedAt)

if _err != nil {
logging.ErrorStackTrace(_err)
return models.Song{}, _err
}

return song, nil
}

func (table *SongTable) FetchSongs(ctx context.Context) (songs []models.Song, error error) {

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

return songs, nil
}

func (table *SongTable) DeleteSongById(songId int) (err error) {
query := "DELETE FROM Song WHERE Id = $1"
return table.NonScalarQuery(query, songId)
}
5 changes: 5 additions & 0 deletions MyMusicBoxApi/http/playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ func (handler *PlaylistHandler) DeletePlaylist(ctx *gin.Context) {
return
}

if DefaultPlaylistId == id {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse("Funky music... "))
return
}

err = handler.PlaylistTable.DeletePlaylist(id)

// TODO delete background image if its not the default image for it
Expand Down
33 changes: 31 additions & 2 deletions MyMusicBoxApi/http/playlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,7 @@ func TestDeletePlaylistPlaylistId(t *testing.T) {

recorder := httptest.NewRecorder()

// Unable to parse to int, will throw error
_route := "/playlist/1"
_route := "/playlist/2"

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

Expand All @@ -276,3 +275,33 @@ func TestDeletePlaylistPlaylistId(t *testing.T) {
// Assert
assert.Equal(t, http.StatusOK, recorder.Code)
}

func TestDeletePlaylistPlaylistIdDefaultPlaylist(t *testing.T) {
// Arrange
route := "/playlist/:playlistId"
router := SetupTestRouter()

mockTable := &mockPlaylistTable{
deletePlaylist: func(playlistId int) (error error) {
return nil
},
}

playlistHandler := PlaylistHandler{
PlaylistTable: mockTable,
}

router.DELETE(route, playlistHandler.DeletePlaylist)

recorder := httptest.NewRecorder()

_route := "/playlist/1"

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

// Act
router.ServeHTTP(recorder, req)

// Assert
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
}
77 changes: 76 additions & 1 deletion MyMusicBoxApi/http/playlistsong.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package http

import (
"bufio"
"fmt"
"musicboxapi/configuration"
"musicboxapi/database"
"musicboxapi/models"
"net/http"
"os"
"strconv"
"strings"

"github.com/gin-gonic/gin"
)

type PlaylistSongHandler struct {
PlaylistsongTable database.IPlaylistsongTable
PlaylistsongTable database.IPlaylistSongTable
}

const DefaultPlaylistId = 1

// @Produce json
// @Param playlistId path int true "Id of playlist"
// @Param lastKnowSongPosition path int false "Last song that is know by the client, pass this in to only get the latest songs"
Expand Down Expand Up @@ -99,5 +105,74 @@ func (handler *PlaylistSongHandler) DeletePlaylistSong(ctx *gin.Context) {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}

// Thumbnail and .opus file will be deleted only if you delete a song via the main playlist containing all the songs
if playlistId == DefaultPlaylistId {

songTable := database.NewSongTableInstance()
song, err := songTable.FetchSongById(songId)

if err != nil {
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}

// Delete actual song file
audioFilePath := song.Path
configuration.DeleteFile(audioFilePath)

// Delete actual thumbnail file
thumbnail := song.ThumbnailPath
thumbnailPath := fmt.Sprintf("%s/images/%s", configuration.Config.SourceFolder, thumbnail)
configuration.DeleteFile(thumbnailPath)

// Delete from database
songTable.DeleteSongById(song.Id)

// Delete from video_archive
strSplit := strings.Split(song.Path, ".")

filenameWithOutExtension := strSplit[0]
strSplit = strings.Split(filenameWithOutExtension, "/")

filenameWithOutExtension = strSplit[1]

filePath := fmt.Sprintf("%s/%s", configuration.Config.SourceFolder, "video_archive")

// Read the file
file, err := os.Open(filePath)
if err != nil {
fmt.Println("Error opening file:", err)
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}
defer file.Close()

var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Keep the line only if it’s not the target
if strings.TrimSpace(line) != fmt.Sprintf("youtube %s", filenameWithOutExtension) {
lines = append(lines, line)
}
}

if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}

// Write the updated content back to the file
output := strings.Join(lines, "\n")
err = os.WriteFile(filePath, []byte(output+"\n"), 0644)
if err != nil {
fmt.Println("Error writing file:", err)
ctx.JSON(http.StatusInternalServerError, models.ErrorResponse(err))
return
}
}

ctx.Status(http.StatusOK)
}
2 changes: 1 addition & 1 deletion MyMusicBoxApi/http/playlistsong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

type mockPlaylistSongTable struct {
database.IPlaylistsongTable
database.IPlaylistSongTable
fetchPlaylistSongs func(ctx context.Context, playlistId int, lastKnowPosition int) (songs []models.Song, error error)
insertPlaylistSong func(playlistId int, songId int) (lastInsertedId int, error error)
deleteAllPlaylistSongs func(playlistId int) (error error) // TODO
Expand Down
2 changes: 2 additions & 0 deletions MyMusicBoxApi/models/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ type ApiResponseModel struct {
}

func ErrorResponse(data any) ApiResponseModel {

return ApiResponseModel{
Data: data,
Message: "An error occurred",
}
}

func OkResponse(data any, message string) ApiResponseModel {
return ApiResponseModel{
Data: data,
Expand Down
3 changes: 3 additions & 0 deletions MyMusicBoxApi/reset
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/bash

# delete dev music and images
sudo rm -r music_dev

# stop dev docker postgres
cd ..
cd dev_database
Expand Down
2 changes: 1 addition & 1 deletion MyMusicBoxApi/service/ytdlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func StartDownloadTask(downloadRequest models.DownloadRequestModel) {

config := configuration.Config
storageFolderName := config.SourceFolder
archiveFileName := fmt.Sprintf("%s/video_archive", storageFolderName)
archiveFileName := fmt.Sprintf("%s/video_archive", storageFolderName) // move to env / congif
idsFileName := fmt.Sprintf("%s/ids.%d", storageFolderName, parentTask.Id)
namesFileName := fmt.Sprintf("%s/names.%d", storageFolderName, parentTask.Id)
durationFileName := fmt.Sprintf("%s/durations.%d", storageFolderName, parentTask.Id)
Expand Down
7 changes: 7 additions & 0 deletions MyMusicClientSveltePwa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion MyMusicClientSveltePwa/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mymusicclientsveltepwa",
"private": true,
"version": "0.1.11",
"version": "0.1.11-beta",
"type": "module",
"scripts": {
"dev": "vite --host",
Expand All @@ -18,6 +18,7 @@
"vite-plugin-pwa": "^1.0.0"
},
"dependencies": {
"@sveltejs/svelte-virtual-list": "^3.0.1",
"@sveltestrap/sveltestrap": "^7.1.0"
}
}
Loading