diff --git a/db.go b/db.go index 0017f1c..1ad5765 100644 --- a/db.go +++ b/db.go @@ -1,6 +1,7 @@ package main import ( + "butterfliu/internal/repository" "butterfliu/migrations" "database/sql" "fmt" @@ -55,10 +56,10 @@ func CloseDB() error { return nil } -func AddLibrary(name, path string) (Library, error) { +func AddLibrary(name, path string) (repository.Library, error) { absPath, err := filepath.Abs(path) if err != nil { - return Library{}, err + return repository.Library{}, err } result, err := DB.Exec( @@ -66,31 +67,31 @@ func AddLibrary(name, path string) (Library, error) { name, absPath, ) if err != nil { - return Library{}, err + return repository.Library{}, err } id, err := result.LastInsertId() if err != nil { - return Library{}, err + return repository.Library{}, err } - return Library{ + return repository.Library{ ID: int(id), Name: name, Path: absPath, }, nil } -func GetLibraries() ([]Library, error) { +func GetLibraries() ([]repository.Library, error) { rows, err := DB.Query("SELECT id, name, path FROM libraries") if err != nil { return nil, err } defer rows.Close() - libraries := []Library{} + libraries := []repository.Library{} for rows.Next() { - var lib Library + var lib repository.Library if err := rows.Scan(&lib.ID, &lib.Name, &lib.Path); err != nil { return nil, err } @@ -100,11 +101,11 @@ func GetLibraries() ([]Library, error) { return libraries, nil } -func GetLibraryByID(id int) (Library, error) { +func GetLibraryByID(id int) (repository.Library, error) { row := DB.QueryRow("SELECT id, name, path FROM libraries WHERE id = ?", id) - var lib Library + var lib repository.Library if err := row.Scan(&lib.ID, &lib.Name, &lib.Path); err != nil { - return Library{}, err + return repository.Library{}, err } return lib, nil } @@ -128,58 +129,58 @@ func DeleteLibrary(id int) error { return err } -func AddMediaFile(path string, libraryID int) (MediaFile, error) { +func AddMediaFile(path string, libraryID int) (repository.MediaFile, error) { result, err := DB.Exec("INSERT INTO media_files (path, library_id) VALUES (?, ?)", path, libraryID) if err != nil { - return MediaFile{}, err + return repository.MediaFile{}, err } id, err := result.LastInsertId() if err != nil { - return MediaFile{}, err + return repository.MediaFile{}, err } - return MediaFile{ + return repository.MediaFile{ ID: int(id), Path: path, }, nil } -func GetMediaFileByPath(path string) (MediaFile, error) { +func GetMediaFileByPath(path string) (repository.MediaFile, error) { row := DB.QueryRow("SELECT id, path FROM media_files WHERE path = ?", path) - var mediaFile MediaFile + var mediaFile repository.MediaFile if err := row.Scan(&mediaFile.ID, &mediaFile.Path); err != nil { - return MediaFile{}, err + return repository.MediaFile{}, err } return mediaFile, nil } -func AddArtist(name string) (Artist, error) { +func AddArtist(name string) (repository.Artist, error) { result, err := DB.Exec("INSERT INTO artists (name) VALUES (?)", name) if err != nil { - return Artist{}, err + return repository.Artist{}, err } id, err := result.LastInsertId() if err != nil { - return Artist{}, err + return repository.Artist{}, err } - return Artist{ + return repository.Artist{ ID: int(id), Name: name, }, nil } -func GetArtists() ([]Artist, error) { +func GetArtists() ([]repository.Artist, error) { rows, err := DB.Query("SELECT id, name FROM artists") if err != nil { return nil, err } defer rows.Close() - artists := []Artist{} + artists := []repository.Artist{} for rows.Next() { - var artist Artist + var artist repository.Artist if err := rows.Scan(&artist.ID, &artist.Name); err != nil { return nil, err } @@ -187,19 +188,19 @@ func GetArtists() ([]Artist, error) { } return artists, nil } -func GetArtistByID(id int) (Artist, error) { +func GetArtistByID(id int) (repository.Artist, error) { row := DB.QueryRow("SELECT id, name FROM artists WHERE id = ?", id) - var artist Artist + var artist repository.Artist if err := row.Scan(&artist.ID, &artist.Name); err != nil { - return Artist{}, err + return repository.Artist{}, err } return artist, nil } -func GetArtistByName(name string) (Artist, error) { +func GetArtistByName(name string) (repository.Artist, error) { row := DB.QueryRow("SELECT id, name FROM artists WHERE name = ?", name) - var artist Artist + var artist repository.Artist if err := row.Scan(&artist.ID, &artist.Name); err != nil { - return Artist{}, err + return repository.Artist{}, err } return artist, nil } @@ -213,32 +214,32 @@ func DeleteArtist(id int) error { return err } -func AddAlbum(title string, artistID int) (Album, error) { +func AddAlbum(title string, artistID int) (repository.Album, error) { result, err := DB.Exec("INSERT INTO albums (title, artist_id) VALUES (?, ?)", title, artistID) if err != nil { - return Album{}, err + return repository.Album{}, err } id, err := result.LastInsertId() if err != nil { - return Album{}, err + return repository.Album{}, err } - return Album{ + return repository.Album{ ID: int(id), Title: title, ArtistID: artistID, }, nil } -func GetAlbums() ([]Album, error) { +func GetAlbums() ([]repository.Album, error) { rows, err := DB.Query("SELECT id, title, artist_id FROM albums") if err != nil { return nil, err } defer rows.Close() - albums := []Album{} + albums := []repository.Album{} for rows.Next() { - var album Album + var album repository.Album if err := rows.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return nil, err } @@ -247,42 +248,42 @@ func GetAlbums() ([]Album, error) { return albums, nil } -func GetAlbumByID(id int) (Album, error) { +func GetAlbumByID(id int) (repository.Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE id = ?", id) - var album Album + var album repository.Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { - return Album{}, err + return repository.Album{}, err } return album, nil } -func GetAlbumByTitle(title string) (Album, error) { +func GetAlbumByTitle(title string) (repository.Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE title = ?", title) - var album Album + var album repository.Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { - return Album{}, err + return repository.Album{}, err } return album, nil } -func GetAlbumByTitleAndArtist(title string, artistID int) (Album, error) { +func GetAlbumByTitleAndArtist(title string, artistID int) (repository.Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE title = ? AND artist_id = ?", title, artistID) - var album Album + var album repository.Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { - return Album{}, err + return repository.Album{}, err } return album, nil } -func GetAlbumsByArtist(artistID int) ([]Album, error) { +func GetAlbumsByArtist(artistID int) ([]repository.Album, error) { rows, err := DB.Query("SELECT id, title, artist_id FROM albums WHERE artist_id = ?", artistID) if err != nil { return nil, err } defer rows.Close() - albums := []Album{} + albums := []repository.Album{} for rows.Next() { - var album Album + var album repository.Album if err := rows.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return nil, err } @@ -304,18 +305,18 @@ func DeleteAlbum(id int) error { return err } -func AddSong(title string, artistID, albumID, duration, mediaFileID int) (Song, error) { +func AddSong(title string, artistID, albumID, duration, mediaFileID int) (repository.Song, error) { result, err := DB.Exec("INSERT INTO songs (title, artist_id, album_id, duration, media_file_id) VALUES (?, ?, ?, ?, ?)", title, artistID, albumID, duration, mediaFileID) if err != nil { - return Song{}, err + return repository.Song{}, err } id, err := result.LastInsertId() if err != nil { - return Song{}, err + return repository.Song{}, err } - return Song{ + return repository.Song{ ID: int(id), Title: title, ArtistID: artistID, @@ -324,15 +325,15 @@ func AddSong(title string, artistID, albumID, duration, mediaFileID int) (Song, MediaFileID: mediaFileID, }, nil } -func GetSongs() ([]Song, error) { +func GetSongs() ([]repository.Song, error) { rows, err := DB.Query("SELECT id, artist_id, album_id, duration, media_file_id FROM songs") if err != nil { return nil, err } defer rows.Close() - songs := []Song{} + songs := []repository.Song{} for rows.Next() { - var song Song + var song repository.Song if err := rows.Scan(&song.ID, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { return nil, err } @@ -361,7 +362,7 @@ func DeleteSong(id int) error { return err } -func GetSongsByLibrary(libraryID int) ([]Song, error) { +func GetSongsByLibrary(libraryID int) ([]repository.Song, error) { rows, err := DB.Query(` SELECT s.id, s.title, s.artist_id, s.album_id, s.duration, s.media_file_id FROM songs s @@ -373,9 +374,9 @@ func GetSongsByLibrary(libraryID int) ([]Song, error) { return nil, err } defer rows.Close() - songs := []Song{} + songs := []repository.Song{} for rows.Next() { - var song Song + var song repository.Song if err := rows.Scan(&song.ID, &song.Title, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { return nil, err } diff --git a/internal/controller/library.go b/internal/controller/library.go index 188a746..a472c49 100644 --- a/internal/controller/library.go +++ b/internal/controller/library.go @@ -153,15 +153,6 @@ func (c *LibraryController) GetSongs(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(songs) } -func (c *LibraryController) GetAllSongs(w http.ResponseWriter, r *http.Request) { - songs, err := c.service.GetAllSongs() - if err != nil { - json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) - return - } - json.NewEncoder(w).Encode(songs) -} - func (c *LibraryController) GetArtists(w http.ResponseWriter, r *http.Request) { artists, err := c.service.GetArtists() if err != nil { diff --git a/internal/controller/song.go b/internal/controller/song.go new file mode 100644 index 0000000..91c903c --- /dev/null +++ b/internal/controller/song.go @@ -0,0 +1,44 @@ +package controller + +import ( + "butterfliu/internal/service" + "encoding/json" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" +) + +type SongController struct { + service *service.SongService +} + +func NewSongController(service *service.SongService) *SongController { + return &SongController{service: service} +} + +func (c *SongController) GetAllWithDetails(w http.ResponseWriter, r *http.Request) { + songs, err := c.service.GetAllWithDetails() + if err != nil { + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } + json.NewEncoder(w).Encode(songs) +} + +func (c *SongController) Stream(w http.ResponseWriter, r *http.Request) { + parmaID := chi.URLParam(r, "id") + id, err := strconv.Atoi(parmaID) + if err != nil { + http.Error(w, "无效的歌曲 ID", http.StatusBadRequest) + return + } + + mediaFile, err := c.service.GetMediaFile(id) + if err != nil { + http.Error(w, "未找到该媒体文件", http.StatusNotFound) + return + } + + http.ServeFile(w, r, mediaFile.Path) +} diff --git a/internal/repository/library.go b/internal/repository/library.go index f88806c..7693278 100644 --- a/internal/repository/library.go +++ b/internal/repository/library.go @@ -4,38 +4,6 @@ import ( "database/sql" ) -type Library struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` -} - -type MediaFile struct { - ID int `json:"id"` - Path string `json:"path"` - LibraryID int `json:"library_id"` -} - -type Artist struct { - ID int `json:"id"` - Name string `json:"name"` -} - -type Album struct { - ID int `json:"id"` - Title string `json:"title"` - ArtistID int `json:"artist_id"` -} - -type Song struct { - ID int `json:"id"` - Title string `json:"title"` - ArtistID int `json:"artist_id"` - AlbumID int `json:"album_id"` - Duration int `json:"duration"` - MediaFileID int `json:"media_file_id"` -} - type SongDetail struct { ID int `json:"id"` Title string `json:"title"` @@ -140,31 +108,6 @@ func (r *LibraryRepository) GetSongsByLibraryWithDetails(libraryID int) ([]SongD return songs, nil } -func (r *LibraryRepository) GetAllSongsWithDetails() ([]SongDetail, error) { - rows, err := r.db.Query(` - SELECT s.id, s.title, a.name as artist_name, al.title as album_title, s.duration, mf.path - FROM songs s - INNER JOIN media_files mf ON s.media_file_id = mf.id - INNER JOIN artists a ON s.artist_id = a.id - INNER JOIN albums al ON s.album_id = al.id - ORDER BY s.title - `) - if err != nil { - return nil, err - } - defer rows.Close() - - songs := []SongDetail{} - for rows.Next() { - var song SongDetail - if err := rows.Scan(&song.ID, &song.Title, &song.Artist, &song.Album, &song.Duration, &song.Path); err != nil { - return nil, err - } - songs = append(songs, song) - } - return songs, nil -} - func (r *LibraryRepository) GetMediaFileByPath(path string) (MediaFile, error) { row := r.db.QueryRow("SELECT id, path, library_id FROM media_files WHERE path = ?", path) var mediaFile MediaFile @@ -300,21 +243,3 @@ func (r *LibraryRepository) GetAlbums() ([]Album, error) { } return albums, nil } - -func (r *LibraryRepository) GetSongs() ([]Song, error) { - rows, err := r.db.Query("SELECT id, title, artist_id, album_id, duration, media_file_id FROM songs") - if err != nil { - return nil, err - } - defer rows.Close() - - songs := []Song{} - for rows.Next() { - var song Song - if err := rows.Scan(&song.ID, &song.Title, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { - return nil, err - } - songs = append(songs, song) - } - return songs, nil -} diff --git a/internal/repository/media_repo.go b/internal/repository/media_repo.go new file mode 100644 index 0000000..5063624 --- /dev/null +++ b/internal/repository/media_repo.go @@ -0,0 +1,26 @@ +package repository + +import "database/sql" + +type MediaRepository struct { + db *sql.DB +} + +func NewMediaRepository(db *sql.DB) *MediaRepository { + return &MediaRepository{db: db} +} + +func (r *MediaRepository) Get(id int) (MediaFile, error) { + var m MediaFile + rows, err := r.db.Query("SELECT id, path, library_id FROM media_files WHERE id = $1", id) + if err != nil { + return MediaFile{}, err + } + defer rows.Close() + if rows.Next() { + if err := rows.Scan(&m.ID, &m.Path, &m.LibraryID); err != nil { + return MediaFile{}, err + } + } + return m, nil +} diff --git a/models.go b/internal/repository/models.go similarity index 97% rename from models.go rename to internal/repository/models.go index 6361a76..984099a 100644 --- a/models.go +++ b/internal/repository/models.go @@ -1,4 +1,4 @@ -package main +package repository type Library struct { ID int `json:"id"` diff --git a/internal/repository/song_repo.go b/internal/repository/song_repo.go new file mode 100644 index 0000000..5636a9b --- /dev/null +++ b/internal/repository/song_repo.go @@ -0,0 +1,67 @@ +package repository + +import "database/sql" + +type SongRepository struct { + db *sql.DB +} + +func NewSongRepository(db *sql.DB) *SongRepository { + return &SongRepository{db: db} +} + +func (r *SongRepository) GetAll() ([]Song, error) { + rows, err := r.db.Query("SELECT id, title, artist_id, album_id, duration, media_file_id FROM songs") + if err != nil { + return nil, err + } + defer rows.Close() + + songs := []Song{} + for rows.Next() { + var song Song + if err := rows.Scan(&song.ID, &song.Title, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { + return nil, err + } + songs = append(songs, song) + } + return songs, nil +} + +func (r *SongRepository) GetAllWithDetails() ([]SongDetail, error) { + rows, err := r.db.Query(` + SELECT s.id, s.title, a.name as artist_name, al.title as album_title, s.duration, mf.path + FROM songs s + INNER JOIN media_files mf ON s.media_file_id = mf.id + INNER JOIN artists a ON s.artist_id = a.id + INNER JOIN albums al ON s.album_id = al.id + ORDER BY s.title + `) + if err != nil { + return nil, err + } + defer rows.Close() + + songs := []SongDetail{} + for rows.Next() { + var song SongDetail + if err := rows.Scan(&song.ID, &song.Title, &song.Artist, &song.Album, &song.Duration, &song.Path); err != nil { + return nil, err + } + songs = append(songs, song) + } + return songs, nil +} + +func (r *SongRepository) Get(id int) (Song, error) { + var song Song + rows, err := r.db.Query("SELECT id, title, artist_id, album_id, duration, media_file_id FROM songs WHERE id = ?", id) + if err != nil { + return Song{}, err + } + defer rows.Close() + if rows.Next() { + rows.Scan(&song.ID, &song.Title, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID) + } + return song, nil +} diff --git a/internal/service/library.go b/internal/service/library.go index d260372..d1dcc8a 100644 --- a/internal/service/library.go +++ b/internal/service/library.go @@ -42,10 +42,6 @@ func (s *LibraryService) GetSongs(id int) ([]repository.SongDetail, error) { return s.repo.GetSongsByLibraryWithDetails(id) } -func (s *LibraryService) GetAllSongs() ([]repository.SongDetail, error) { - return s.repo.GetAllSongsWithDetails() -} - func (s *LibraryService) GetArtists() ([]repository.Artist, error) { return s.repo.GetArtists() } diff --git a/internal/service/song_svc.go b/internal/service/song_svc.go new file mode 100644 index 0000000..dee18fd --- /dev/null +++ b/internal/service/song_svc.go @@ -0,0 +1,32 @@ +package service + +import "butterfliu/internal/repository" + +type SongService struct { + songRepo *repository.SongRepository + mediaRepo *repository.MediaRepository +} + +func NewSongService(songRepo *repository.SongRepository, mediaRepo *repository.MediaRepository) *SongService { + return &SongService{songRepo, mediaRepo} +} + +func (s *SongService) GetAll() ([]repository.Song, error) { + return s.songRepo.GetAll() +} + +func (s *SongService) GetAllWithDetails() ([]repository.SongDetail, error) { + return s.songRepo.GetAllWithDetails() +} + +func (service *SongService) GetMediaFile(id int) (repository.MediaFile, error) { + song, err := service.songRepo.Get(id) + if err != nil { + return repository.MediaFile{}, err + } + media, err := service.mediaRepo.Get(song.MediaFileID) + if err != nil { + return repository.MediaFile{}, err + } + return media, nil +} diff --git a/main.go b/main.go index 3b0344a..a5d4b1f 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,12 @@ func main() { r.Use(middleware.Logger) libraryRepo := repository.NewLibraryRepository(GetDB()) + songRepo := repository.NewSongRepository(GetDB()) + mediaRepo := repository.NewMediaRepository(GetDB()) libraryService := service.NewLibraryService(libraryRepo) + songService := service.NewSongService(songRepo, mediaRepo) libraryController := controller.NewLibraryController(libraryService) + songController := controller.NewSongController(songService) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World!")) @@ -39,8 +43,12 @@ func main() { r.Get("/{id}/songs", libraryController.GetSongs) }) + r.Route("/api/songs", func(r chi.Router) { + r.Get("/", songController.GetAllWithDetails) + r.Get("/{id}/stream", songController.Stream) + }) + r.Route("/api", func(r chi.Router) { - r.Get("/songs", libraryController.GetAllSongs) r.Get("/artists", libraryController.GetArtists) r.Get("/albums", libraryController.GetAlbums) })