mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
feat: v0.0.8
This commit is contained in:
parent
00e7782be2
commit
80b6f4deaa
66 changed files with 1559 additions and 916 deletions
|
|
@ -1,7 +1,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -40,44 +39,43 @@ func GetAliasesHandler(store db.DB) http.HandlerFunc {
|
|||
if artistIDStr != "" {
|
||||
artistID, err := strconv.Atoi(artistIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Invalid artist id")
|
||||
l.Debug().AnErr("error", err).Msg("GetAliasesHandler: Invalid artist id")
|
||||
utils.WriteError(w, "invalid artist_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
aliases, err = store.GetAllArtistAliases(ctx, int32(artistID))
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Failed to get artist aliases")
|
||||
l.Err(err).Msg("GetAliasesHandler: Failed to get artist aliases")
|
||||
utils.WriteError(w, "failed to retrieve aliases", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if albumIDStr != "" {
|
||||
albumID, err := strconv.Atoi(albumIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Invalid album id")
|
||||
l.Debug().AnErr("error", err).Msg("GetAliasesHandler: Invalid album id")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
aliases, err = store.GetAllAlbumAliases(ctx, int32(albumID))
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Failed to get album aliases")
|
||||
l.Err(err).Msg("GetAliasesHandler: Failed to get album aliases")
|
||||
utils.WriteError(w, "failed to retrieve aliases", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if trackIDStr != "" {
|
||||
trackID, err := strconv.Atoi(trackIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Invalid track id")
|
||||
l.Debug().AnErr("error", err).Msg("GetAliasesHandler: Invalid track id")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
aliases, err = store.GetAllTrackAliases(ctx, int32(trackID))
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GetAliasesHandler: %w", err)).Msg("Failed to get track aliases")
|
||||
l.Err(err).Msg("GetAliasesHandler: Failed to get track aliases")
|
||||
utils.WriteError(w, "failed to retrieve aliases", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, aliases)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +86,7 @@ func DeleteAliasHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("DeleteAliasHandler: Got request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("DeleteAliasHandler: Got request")
|
||||
|
||||
// Parse query parameters
|
||||
artistIDStr := r.URL.Query().Get("artist_id")
|
||||
|
|
@ -97,52 +95,56 @@ func DeleteAliasHandler(store db.DB) http.HandlerFunc {
|
|||
alias := r.URL.Query().Get("alias")
|
||||
|
||||
if alias == "" || (artistIDStr == "" && albumIDStr == "" && trackIDStr == "") {
|
||||
l.Debug().Msgf("DeleteAliasHandler: Request is missing required parameters")
|
||||
l.Debug().Msg("DeleteAliasHandler: Request is missing required parameters")
|
||||
utils.WriteError(w, "alias and artist_id, album_id, or track_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if utils.MoreThanOneString(artistIDStr, albumIDStr, trackIDStr) {
|
||||
l.Debug().Msgf("DeleteAliasHandler: Request is has more than one of artist_id, album_id, and track_id")
|
||||
l.Debug().Msg("DeleteAliasHandler: Request has more than one of artist_id, album_id, and track_id")
|
||||
utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided at a time", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if artistIDStr != "" {
|
||||
artistID, err := strconv.Atoi(artistIDStr)
|
||||
var artistID int
|
||||
artistID, err = strconv.Atoi(artistIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Invalid artist id")
|
||||
l.Debug().AnErr("error", err).Msg("DeleteAliasHandler: Invalid artist id")
|
||||
utils.WriteError(w, "invalid artist_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.DeleteArtistAlias(ctx, int32(artistID), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Failed to delete artist alias")
|
||||
l.Error().Err(err).Msg("DeleteAliasHandler: Failed to delete artist alias")
|
||||
utils.WriteError(w, "failed to delete alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if albumIDStr != "" {
|
||||
albumID, err := strconv.Atoi(albumIDStr)
|
||||
var albumID int
|
||||
albumID, err = strconv.Atoi(albumIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Invalid album id")
|
||||
l.Debug().AnErr("error", err).Msg("DeleteAliasHandler: Invalid album id")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.DeleteAlbumAlias(ctx, int32(albumID), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Failed to delete album alias")
|
||||
l.Error().Err(err).Msg("DeleteAliasHandler: Failed to delete album alias")
|
||||
utils.WriteError(w, "failed to delete alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if trackIDStr != "" {
|
||||
trackID, err := strconv.Atoi(trackIDStr)
|
||||
var trackID int
|
||||
trackID, err = strconv.Atoi(trackIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Invalid track id")
|
||||
l.Debug().AnErr("error", err).Msg("DeleteAliasHandler: Invalid track id")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.DeleteTrackAlias(ctx, int32(trackID), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("DeleteAliasHandler: %w", err)).Msg("Failed to delete track alias")
|
||||
l.Error().Err(err).Msg("DeleteAliasHandler: Failed to delete track alias")
|
||||
utils.WriteError(w, "failed to delete alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -158,16 +160,18 @@ func CreateAliasHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("CreateAliasHandler: Got request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("CreateAliasHandler: Got request")
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("CreateAliasHandler: Failed to parse form")
|
||||
utils.WriteError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
alias := r.FormValue("alias")
|
||||
if alias == "" {
|
||||
l.Debug().Msg("CreateAliasHandler: Alias parameter missing")
|
||||
utils.WriteError(w, "alias must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
@ -176,53 +180,54 @@ func CreateAliasHandler(store db.DB) http.HandlerFunc {
|
|||
albumIDStr := r.URL.Query().Get("album_id")
|
||||
trackIDStr := r.URL.Query().Get("track_id")
|
||||
|
||||
if alias == "" || (artistIDStr == "" && albumIDStr == "" && trackIDStr == "") {
|
||||
l.Debug().Msgf("CreateAliasHandler: Request is missing required parameters")
|
||||
utils.WriteError(w, "alias and artist_id, album_id, or track_id must be provided", http.StatusBadRequest)
|
||||
if artistIDStr == "" && albumIDStr == "" && trackIDStr == "" {
|
||||
l.Debug().Msg("CreateAliasHandler: Missing ID parameter")
|
||||
utils.WriteError(w, "artist_id, album_id, or track_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if utils.MoreThanOneString(artistIDStr, albumIDStr, trackIDStr) {
|
||||
l.Debug().Msgf("CreateAliasHandler: Request is has more than one of artist_id, album_id, and track_id")
|
||||
utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided at a time", http.StatusBadRequest)
|
||||
l.Debug().Msg("CreateAliasHandler: Multiple ID parameters provided")
|
||||
utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var id int
|
||||
if artistIDStr != "" {
|
||||
artistID, err := strconv.Atoi(artistIDStr)
|
||||
id, err = strconv.Atoi(artistIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Invalid artist id")
|
||||
l.Debug().AnErr("error", err).Msg("CreateAliasHandler: Invalid artist id")
|
||||
utils.WriteError(w, "invalid artist_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SaveArtistAliases(ctx, int32(artistID), []string{alias}, "Manual")
|
||||
err = store.SaveArtistAliases(ctx, int32(id), []string{alias}, "Manual")
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Failed to save artist alias")
|
||||
l.Error().Err(err).Msg("CreateAliasHandler: Failed to save artist alias")
|
||||
utils.WriteError(w, "failed to save alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if albumIDStr != "" {
|
||||
albumID, err := strconv.Atoi(albumIDStr)
|
||||
id, err = strconv.Atoi(albumIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Invalid album id")
|
||||
l.Debug().AnErr("error", err).Msg("CreateAliasHandler: Invalid album id")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SaveAlbumAliases(ctx, int32(albumID), []string{alias}, "Manual")
|
||||
err = store.SaveAlbumAliases(ctx, int32(id), []string{alias}, "Manual")
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Failed to save album alias")
|
||||
l.Error().Err(err).Msg("CreateAliasHandler: Failed to save album alias")
|
||||
utils.WriteError(w, "failed to save alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if trackIDStr != "" {
|
||||
trackID, err := strconv.Atoi(trackIDStr)
|
||||
id, err = strconv.Atoi(trackIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Invalid track id")
|
||||
l.Debug().AnErr("error", err).Msg("CreateAliasHandler: Invalid track id")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SaveTrackAliases(ctx, int32(trackID), []string{alias}, "Manual")
|
||||
err = store.SaveTrackAliases(ctx, int32(id), []string{alias}, "Manual")
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("CreateAliasHandler: %w", err)).Msg("Failed to save track alias")
|
||||
l.Error().Err(err).Msg("CreateAliasHandler: Failed to save track alias")
|
||||
utils.WriteError(w, "failed to save alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -238,7 +243,7 @@ func SetPrimaryAliasHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("SetPrimaryAliasHandler: Got request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("SetPrimaryAliasHandler: Got request")
|
||||
|
||||
// Parse query parameters
|
||||
artistIDStr := r.URL.Query().Get("artist_id")
|
||||
|
|
@ -246,53 +251,60 @@ func SetPrimaryAliasHandler(store db.DB) http.HandlerFunc {
|
|||
trackIDStr := r.URL.Query().Get("track_id")
|
||||
alias := r.URL.Query().Get("alias")
|
||||
|
||||
if alias == "" || (artistIDStr == "" && albumIDStr == "" && trackIDStr == "") {
|
||||
l.Debug().Msgf("SetPrimaryAliasHandler: Request is missing required parameters")
|
||||
utils.WriteError(w, "alias and artist_id, album_id, or track_id must be provided", http.StatusBadRequest)
|
||||
if alias == "" {
|
||||
l.Debug().Msg("SetPrimaryAliasHandler: Missing alias parameter")
|
||||
utils.WriteError(w, "alias must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if artistIDStr == "" && albumIDStr == "" && trackIDStr == "" {
|
||||
l.Debug().Msg("SetPrimaryAliasHandler: Missing ID parameter")
|
||||
utils.WriteError(w, "artist_id, album_id, or track_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if utils.MoreThanOneString(artistIDStr, albumIDStr, trackIDStr) {
|
||||
l.Debug().Msgf("SetPrimaryAliasHandler: Request is has more than one of artist_id, album_id, and track_id")
|
||||
utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided at a time", http.StatusBadRequest)
|
||||
l.Debug().Msg("SetPrimaryAliasHandler: Multiple ID parameters provided")
|
||||
utils.WriteError(w, "only one of artist_id, album_id, or track_id can be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var id int
|
||||
var err error
|
||||
if artistIDStr != "" {
|
||||
artistID, err := strconv.Atoi(artistIDStr)
|
||||
id, err = strconv.Atoi(artistIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Invalid artist id")
|
||||
l.Debug().AnErr("error", err).Msg("SetPrimaryAliasHandler: Invalid artist id")
|
||||
utils.WriteError(w, "invalid artist_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SetPrimaryArtistAlias(ctx, int32(artistID), alias)
|
||||
err = store.SetPrimaryArtistAlias(ctx, int32(id), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Failed to set artist primary alias")
|
||||
l.Error().Err(err).Msg("SetPrimaryAliasHandler: Failed to set artist primary alias")
|
||||
utils.WriteError(w, "failed to set primary alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if albumIDStr != "" {
|
||||
albumID, err := strconv.Atoi(albumIDStr)
|
||||
id, err = strconv.Atoi(albumIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Invalid album id")
|
||||
l.Debug().AnErr("error", err).Msg("SetPrimaryAliasHandler: Invalid album id")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SetPrimaryAlbumAlias(ctx, int32(albumID), alias)
|
||||
err = store.SetPrimaryAlbumAlias(ctx, int32(id), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Failed to set album primary alias")
|
||||
l.Error().Err(err).Msg("SetPrimaryAliasHandler: Failed to set album primary alias")
|
||||
utils.WriteError(w, "failed to set primary alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if trackIDStr != "" {
|
||||
trackID, err := strconv.Atoi(trackIDStr)
|
||||
id, err = strconv.Atoi(trackIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Invalid track id")
|
||||
l.Debug().AnErr("error", err).Msg("SetPrimaryAliasHandler: Invalid track id")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SetPrimaryTrackAlias(ctx, int32(trackID), alias)
|
||||
err = store.SetPrimaryTrackAlias(ctx, int32(id), alias)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("SetPrimaryAliasHandler: %w", err)).Msg("Failed to set track primary alias")
|
||||
l.Error().Err(err).Msg("SetPrimaryAliasHandler: Failed to set track primary alias")
|
||||
utils.WriteError(w, "failed to set primary alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -16,45 +15,47 @@ func GenerateApiKeyHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("GenerateApiKeyHandler: Received request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("GenerateApiKeyHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("GenerateApiKeyHandler: Invalid user retrieved from context")
|
||||
l.Debug().Msg("GenerateApiKeyHandler: Invalid user context")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
if err := r.ParseForm(); err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("GenerateApiKeyHandler: Failed to parse form")
|
||||
utils.WriteError(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
label := r.FormValue("label")
|
||||
if label == "" {
|
||||
l.Debug().Msg("GenerateApiKeyHandler: Request rejected due to missing label")
|
||||
l.Debug().Msg("GenerateApiKeyHandler: Missing label parameter")
|
||||
utils.WriteError(w, "label is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
apiKey, err := utils.GenerateRandomString(48)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GenerateApiKeyHandler: %w", err)).Msg("Failed to generate API key")
|
||||
l.Error().Err(err).Msg("GenerateApiKeyHandler: Failed to generate API key")
|
||||
utils.WriteError(w, "failed to generate api key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
opts := db.SaveApiKeyOpts{
|
||||
key, err := store.SaveApiKey(ctx, db.SaveApiKeyOpts{
|
||||
UserID: user.ID,
|
||||
Key: apiKey,
|
||||
Label: label,
|
||||
}
|
||||
l.Debug().Msgf("GenerateApiKeyHandler: Saving API key with options: %+v", opts)
|
||||
|
||||
key, err := store.SaveApiKey(ctx, opts)
|
||||
})
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GenerateApiKeyHandler: %w", err)).Msg("Failed to save API key")
|
||||
l.Error().Err(err).Msg("GenerateApiKeyHandler: Failed to save API key")
|
||||
utils.WriteError(w, "failed to save api key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("GenerateApiKeyHandler: Successfully saved API key with ID: %d", key.ID)
|
||||
l.Debug().Msgf("GenerateApiKeyHandler: Successfully generated API key ID %d", key.ID)
|
||||
utils.WriteJSON(w, http.StatusCreated, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -64,39 +65,36 @@ func DeleteApiKeyHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("DeleteApiKeyHandler: Received request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("DeleteApiKeyHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("DeleteApiKeyHandler: User could not be verified (context user is nil)")
|
||||
l.Debug().Msg("DeleteApiKeyHandler: Invalid user context")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
idStr := r.URL.Query().Get("id")
|
||||
if idStr == "" {
|
||||
l.Debug().Msg("DeleteApiKeyHandler: Request rejected due to missing ID")
|
||||
l.Debug().Msg("DeleteApiKeyHandler: Missing id parameter")
|
||||
utils.WriteError(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
apiKey, err := strconv.Atoi(idStr)
|
||||
apiKeyID, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("DeleteApiKeyHandler: %w", err)).Msg("Invalid API key ID")
|
||||
utils.WriteError(w, "id is invalid", http.StatusBadRequest)
|
||||
l.Debug().AnErr("error", err).Msg("DeleteApiKeyHandler: Invalid API key ID")
|
||||
utils.WriteError(w, "invalid id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("DeleteApiKeyHandler: Deleting API key with ID: %d", apiKey)
|
||||
|
||||
err = store.DeleteApiKey(ctx, int32(apiKey))
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("DeleteApiKeyHandler: %w", err)).Msg("Failed to delete API key")
|
||||
if err := store.DeleteApiKey(ctx, int32(apiKeyID)); err != nil {
|
||||
l.Error().Err(err).Msg("DeleteApiKeyHandler: Failed to delete API key")
|
||||
utils.WriteError(w, "failed to delete api key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("DeleteApiKeyHandler: Successfully deleted API key with ID: %d", apiKey)
|
||||
l.Debug().Msgf("DeleteApiKeyHandler: Successfully deleted API key ID %d", apiKeyID)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
|
@ -106,25 +104,23 @@ func GetApiKeysHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msgf("GetApiKeysHandler: Received request with params: '%s'", r.URL.Query().Encode())
|
||||
l.Debug().Msg("GetApiKeysHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("GetApiKeysHandler: Invalid user retrieved from context")
|
||||
l.Debug().Msg("GetApiKeysHandler: Invalid user context")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("GetApiKeysHandler: Retrieving API keys for user ID: %d", user.ID)
|
||||
|
||||
apiKeys, err := store.GetApiKeysByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("GetApiKeysHandler: %w", err)).Msg("Failed to retrieve API keys")
|
||||
l.Error().Err(err).Msg("GetApiKeysHandler: Failed to retrieve API keys")
|
||||
utils.WriteError(w, "failed to retrieve api keys", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("GetApiKeysHandler: Successfully retrieved %d API keys for user ID: %d", len(apiKeys), user.ID)
|
||||
l.Debug().Msgf("GetApiKeysHandler: Retrieved %d API keys", len(apiKeys))
|
||||
utils.WriteJSON(w, http.StatusOK, apiKeys)
|
||||
}
|
||||
}
|
||||
|
|
@ -134,45 +130,42 @@ func UpdateApiKeyLabelHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Received request to update API key label")
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Unauthorized request (user context is nil)")
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Invalid user context")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
idStr := r.URL.Query().Get("id")
|
||||
if idStr == "" {
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Missing API key ID in request")
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Missing id parameter")
|
||||
utils.WriteError(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
apiKeyID, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", fmt.Errorf("UpdateApiKeyLabelHandler: %w", err)).Msg("Invalid API key ID")
|
||||
utils.WriteError(w, "id is invalid", http.StatusBadRequest)
|
||||
l.Debug().AnErr("error", err).Msg("UpdateApiKeyLabelHandler: Invalid API key ID")
|
||||
utils.WriteError(w, "invalid id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
label := r.FormValue("label")
|
||||
if label == "" {
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Missing label in request")
|
||||
l.Debug().Msg("UpdateApiKeyLabelHandler: Missing label parameter")
|
||||
utils.WriteError(w, "label is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("UpdateApiKeyLabelHandler: Updating label for API key ID %d", apiKeyID)
|
||||
|
||||
err = store.UpdateApiKeyLabel(ctx, db.UpdateApiKeyLabelOpts{
|
||||
if err := store.UpdateApiKeyLabel(ctx, db.UpdateApiKeyLabelOpts{
|
||||
UserID: user.ID,
|
||||
ID: int32(apiKeyID),
|
||||
Label: label,
|
||||
})
|
||||
if err != nil {
|
||||
l.Err(fmt.Errorf("UpdateApiKeyLabelHandler: %w", err)).Msg("Failed to update API key label")
|
||||
}); err != nil {
|
||||
l.Error().Err(err).Msg("UpdateApiKeyLabelHandler: Failed to update API key label")
|
||||
utils.WriteError(w, "failed to update api key label", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
156
engine/handlers/artists.go
Normal file
156
engine/handlers/artists.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/gabehf/koito/internal/logger"
|
||||
"github.com/gabehf/koito/internal/models"
|
||||
"github.com/gabehf/koito/internal/utils"
|
||||
)
|
||||
|
||||
func SetPrimaryArtistHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// sets the primary alias for albums, artists, and tracks
|
||||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: Got request")
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
// Parse query parameters
|
||||
artistIDStr := r.FormValue("artist_id")
|
||||
albumIDStr := r.FormValue("album_id")
|
||||
trackIDStr := r.FormValue("track_id")
|
||||
isPrimaryStr := r.FormValue("is_primary")
|
||||
|
||||
l.Debug().Str("query", r.Form.Encode()).Msg("Recieved form")
|
||||
|
||||
if artistIDStr == "" {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: artist_id must be provided")
|
||||
utils.WriteError(w, "artist_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if isPrimaryStr == "" {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: is_primary must be provided")
|
||||
utils.WriteError(w, "is_primary must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
primary, ok := utils.ParseBool(isPrimaryStr)
|
||||
if !ok {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: is_primary must be either true or false")
|
||||
utils.WriteError(w, "is_primary must be either true or false", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
artistId, err := strconv.Atoi(artistIDStr)
|
||||
if err != nil {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: artist_id is invalid")
|
||||
utils.WriteError(w, "artist_id is invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if albumIDStr == "" && trackIDStr == "" {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: Missing album or track id parameter")
|
||||
utils.WriteError(w, "album_id or track_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if utils.MoreThanOneString(albumIDStr, trackIDStr) {
|
||||
l.Debug().Msg("SetPrimaryArtistHandler: Multiple ID parameters provided")
|
||||
utils.WriteError(w, "only one of album_id or track_id can be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if albumIDStr != "" {
|
||||
id, err := strconv.Atoi(albumIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("SetPrimaryArtistHandler: Invalid album id")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SetPrimaryAlbumArtist(ctx, int32(id), int32(artistId), primary)
|
||||
if err != nil {
|
||||
l.Error().Err(err).Msg("SetPrimaryArtistHandler: Failed to set album primary alias")
|
||||
utils.WriteError(w, "failed to set primary alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if trackIDStr != "" {
|
||||
id, err := strconv.Atoi(trackIDStr)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("SetPrimaryArtistHandler: Invalid track id")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = store.SetPrimaryTrackArtist(ctx, int32(id), int32(artistId), primary)
|
||||
if err != nil {
|
||||
l.Error().Err(err).Msg("SetPrimaryArtistHandler: Failed to set track primary alias")
|
||||
utils.WriteError(w, "failed to set primary alias", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
func GetArtistsForItemHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("GetArtistsForItemHandler: Received request to retrieve artists for item")
|
||||
|
||||
albumIDStr := r.URL.Query().Get("album_id")
|
||||
trackIDStr := r.URL.Query().Get("track_id")
|
||||
|
||||
if albumIDStr == "" && trackIDStr == "" {
|
||||
l.Debug().Msg("GetArtistsForItemHandler: Missing album or track ID parameter")
|
||||
utils.WriteError(w, "album_id or track_id must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if utils.MoreThanOneString(albumIDStr, trackIDStr) {
|
||||
l.Debug().Msg("GetArtistsForItemHandler: Multiple ID parameters provided")
|
||||
utils.WriteError(w, "only one of album_id or track_id can be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var artists []*models.Artist
|
||||
var err error
|
||||
|
||||
if albumIDStr != "" {
|
||||
albumID, convErr := strconv.Atoi(albumIDStr)
|
||||
if convErr != nil {
|
||||
l.Debug().AnErr("error", convErr).Msg("GetArtistsForItemHandler: Invalid album ID")
|
||||
utils.WriteError(w, "invalid album_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("GetArtistsForItemHandler: Fetching artists for album ID %d", albumID)
|
||||
artists, err = store.GetArtistsForAlbum(ctx, int32(albumID))
|
||||
} else if trackIDStr != "" {
|
||||
trackID, convErr := strconv.Atoi(trackIDStr)
|
||||
if convErr != nil {
|
||||
l.Debug().AnErr("error", convErr).Msg("GetArtistsForItemHandler: Invalid track ID")
|
||||
utils.WriteError(w, "invalid track_id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("GetArtistsForItemHandler: Fetching artists for track ID %d", trackID)
|
||||
artists, err = store.GetArtistsForTrack(ctx, int32(trackID))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.Err(err).Msg("GetArtistsForItemHandler: Failed to retrieve artists")
|
||||
utils.WriteError(w, "failed to retrieve artists", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msg("GetArtistsForItemHandler: Successfully retrieved artists")
|
||||
utils.WriteJSON(w, http.StatusOK, artists)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,70 +18,62 @@ func LoginHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("LoginHandler: Received login request")
|
||||
l.Debug().Msg("LoginHandler: Received request")
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
l.Debug().Msg("LoginHandler: Failed to parse request form")
|
||||
utils.WriteError(w, "failed to parse request", http.StatusInternalServerError)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("LoginHandler: Failed to parse form")
|
||||
utils.WriteError(w, "invalid request format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
if username == "" || password == "" {
|
||||
l.Debug().Msg("LoginHandler: Missing username or password")
|
||||
utils.WriteError(w, "username and password are required", http.StatusBadRequest)
|
||||
l.Debug().Msg("LoginHandler: Missing credentials")
|
||||
utils.WriteError(w, "username and password required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("LoginHandler: Searching for user with username '%s'", username)
|
||||
user, err := store.GetUserByUsername(ctx, username)
|
||||
if err != nil {
|
||||
l.Err(err).Msg("LoginHandler: Error searching for user in database")
|
||||
utils.WriteError(w, "internal server error", http.StatusInternalServerError)
|
||||
l.Error().Err(err).Msg("LoginHandler: Database error fetching user")
|
||||
utils.WriteError(w, "authentication failed", http.StatusInternalServerError)
|
||||
return
|
||||
} else if user == nil {
|
||||
l.Debug().Msg("LoginHandler: Username or password is incorrect")
|
||||
utils.WriteError(w, "username or password is incorrect", http.StatusBadRequest)
|
||||
}
|
||||
if user == nil {
|
||||
l.Debug().Msg("LoginHandler: User not found")
|
||||
utils.WriteError(w, "invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(user.Password, []byte(password))
|
||||
if err != nil {
|
||||
l.Debug().Msg("LoginHandler: Password comparison failed")
|
||||
utils.WriteError(w, "username or password is incorrect", http.StatusBadRequest)
|
||||
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(password)); err != nil {
|
||||
l.Debug().Msg("LoginHandler: Invalid password")
|
||||
utils.WriteError(w, "invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
keepSignedIn := false
|
||||
expiresAt := time.Now().Add(1 * 24 * time.Hour)
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
if strings.ToLower(r.FormValue("remember_me")) == "true" {
|
||||
keepSignedIn = true
|
||||
expiresAt = time.Now().Add(30 * 24 * time.Hour)
|
||||
}
|
||||
|
||||
l.Debug().Msgf("LoginHandler: Creating session for user ID %d", user.ID)
|
||||
session, err := store.SaveSession(ctx, user.ID, expiresAt, keepSignedIn)
|
||||
session, err := store.SaveSession(ctx, user.ID, expiresAt, r.FormValue("remember_me") == "true")
|
||||
if err != nil {
|
||||
l.Err(err).Msg("LoginHandler: Failed to create session")
|
||||
utils.WriteError(w, "failed to create session", http.StatusInternalServerError)
|
||||
l.Error().Err(err).Msg("LoginHandler: Failed to create session")
|
||||
utils.WriteError(w, "authentication failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "koito_session",
|
||||
Value: session.ID.String(),
|
||||
Expires: expiresAt,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
}
|
||||
})
|
||||
|
||||
if keepSignedIn {
|
||||
cookie.Expires = expiresAt
|
||||
}
|
||||
|
||||
l.Debug().Msgf("LoginHandler: Session created successfully for user ID %d", user.ID)
|
||||
http.SetCookie(w, cookie)
|
||||
l.Debug().Msgf("LoginHandler: User %d authenticated", user.ID)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,34 +83,27 @@ func LogoutHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("LogoutHandler: Received logout request")
|
||||
l.Debug().Msg("LogoutHandler: Received request")
|
||||
|
||||
cookie, err := r.Cookie("koito_session")
|
||||
if err == nil {
|
||||
l.Debug().Msg("LogoutHandler: Found session cookie")
|
||||
sid, err := uuid.Parse(cookie.Value)
|
||||
if err != nil {
|
||||
l.Debug().AnErr("error", err).Msg("LogoutHandler: Invalid session cookie")
|
||||
utils.WriteError(w, "session cookie is invalid", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
l.Debug().Msgf("LogoutHandler: Deleting session with ID %s", sid)
|
||||
err = store.DeleteSession(ctx, sid)
|
||||
if err != nil {
|
||||
l.Err(err).Msg("LogoutHandler: Failed to delete session")
|
||||
utils.WriteError(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
l.Debug().AnErr("error", err).Msg("LogoutHandler: Invalid session ID")
|
||||
} else if err := store.DeleteSession(ctx, sid); err != nil {
|
||||
l.Error().Err(err).Msg("LogoutHandler: Failed to delete session")
|
||||
}
|
||||
}
|
||||
|
||||
l.Debug().Msg("LogoutHandler: Clearing session cookie")
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "koito_session",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
MaxAge: -1, // expire immediately
|
||||
MaxAge: -1,
|
||||
})
|
||||
|
||||
l.Debug().Msg("LogoutHandler: Session terminated")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,16 +113,17 @@ func MeHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("MeHandler: Received request to retrieve user information")
|
||||
u := middleware.GetUserFromContext(ctx)
|
||||
if u == nil {
|
||||
l.Debug().Msg("MeHandler: Invalid user retrieved from context")
|
||||
l.Debug().Msg("MeHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("MeHandler: Unauthorized access")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("MeHandler: Successfully retrieved user with ID %d", u.ID)
|
||||
utils.WriteJSON(w, http.StatusOK, u)
|
||||
l.Debug().Msgf("MeHandler: Returning user data for ID %d", user.ID)
|
||||
utils.WriteJSON(w, http.StatusOK, user)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,41 +132,42 @@ func UpdateUserHandler(store db.DB) http.HandlerFunc {
|
|||
ctx := r.Context()
|
||||
l := logger.FromContext(ctx)
|
||||
|
||||
l.Debug().Msg("UpdateUserHandler: Received request to update user information")
|
||||
u := middleware.GetUserFromContext(ctx)
|
||||
if u == nil {
|
||||
l.Debug().Msg("UpdateUserHandler: Unauthorized request (user context is nil)")
|
||||
l.Debug().Msg("UpdateUserHandler: Received request")
|
||||
|
||||
user := middleware.GetUserFromContext(ctx)
|
||||
if user == nil {
|
||||
l.Debug().Msg("UpdateUserHandler: Unauthorized access")
|
||||
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
l.Err(err).Msg("UpdateUserHandler: Failed to parse request form")
|
||||
utils.WriteError(w, "failed to parse request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
if username == "" && password == "" {
|
||||
l.Debug().Msg("UpdateUserHandler: No parameters were recieved")
|
||||
utils.WriteError(w, "all parameters missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
l.Debug().Msgf("UpdateUserHandler: Updating user with ID %d", u.ID)
|
||||
err = store.UpdateUser(ctx, db.UpdateUserOpts{
|
||||
ID: u.ID,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
l.Err(err).Msg("UpdateUserHandler: Failed to update user")
|
||||
utils.WriteError(w, err.Error(), http.StatusBadRequest)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
l.Error().Err(err).Msg("UpdateUserHandler: Invalid form data")
|
||||
utils.WriteError(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("UpdateUserHandler: Successfully updated user with ID %d", u.ID)
|
||||
opts := db.UpdateUserOpts{ID: user.ID}
|
||||
if username := r.FormValue("username"); username != "" {
|
||||
opts.Username = username
|
||||
}
|
||||
if password := r.FormValue("password"); password != "" {
|
||||
opts.Password = password
|
||||
}
|
||||
|
||||
if opts.Username == "" && opts.Password == "" {
|
||||
l.Debug().Msg("UpdateUserHandler: No update parameters provided")
|
||||
utils.WriteError(w, "no changes specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.UpdateUser(ctx, opts); err != nil {
|
||||
l.Error().Err(err).Msg("UpdateUserHandler: Update failed")
|
||||
utils.WriteError(w, "update failed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
l.Debug().Msgf("UpdateUserHandler: User %d updated", user.ID)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/gabehf/koito/internal/utils"
|
||||
)
|
||||
|
||||
// DeleteTrackHandler deletes a track by its ID.
|
||||
func DeleteTrackHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
|
@ -46,7 +45,6 @@ func DeleteTrackHandler(store db.DB) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// DeleteListenHandler deletes a listen record by track ID and timestamp.
|
||||
func DeleteListenHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
|
@ -96,7 +94,6 @@ func DeleteListenHandler(store db.DB) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// DeleteArtistHandler deletes an artist by its ID.
|
||||
func DeleteArtistHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
|
@ -132,7 +129,6 @@ func DeleteArtistHandler(store db.DB) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// DeleteAlbumHandler deletes an album by its ID.
|
||||
func DeleteAlbumHandler(store db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
|
|
|||
|
|
@ -117,7 +117,12 @@ func serveDefaultImage(w http.ResponseWriter, r *http.Request, size catalog.Imag
|
|||
return
|
||||
}
|
||||
lock.Lock()
|
||||
utils.CopyFile(path.Join("assets", "default_img"), defaultImagePath)
|
||||
err = utils.CopyFile(path.Join("assets", "default_img"), defaultImagePath)
|
||||
if err != nil {
|
||||
l.Err(err).Msg("serveDefaultImage: Error when copying default image from assets")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
lock.Unlock()
|
||||
} else if err != nil {
|
||||
l.Err(err).Msg("serveDefaultImage: Error when attempting to read default image in cache")
|
||||
|
|
@ -151,7 +156,7 @@ func serveDefaultImage(w http.ResponseWriter, r *http.Request, size catalog.Imag
|
|||
func downloadMissingImage(ctx context.Context, store db.DB, id uuid.UUID) (string, error) {
|
||||
src, err := store.GetImageSource(ctx, id)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("downloadMissingImage: store.GetImageSource: %w", err)
|
||||
return "", fmt.Errorf("downloadMissingImage: %w", err)
|
||||
}
|
||||
var size catalog.ImageSize
|
||||
if cfg.FullImageCacheEnabled() {
|
||||
|
|
@ -161,7 +166,7 @@ func downloadMissingImage(ctx context.Context, store db.DB, id uuid.UUID) (strin
|
|||
}
|
||||
err = catalog.DownloadAndCacheImage(ctx, id, src, size)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("downloadMissingImage: catalog.DownloadAndCacheImage: %w", err)
|
||||
return "", fmt.Errorf("downloadMissingImage: %w", err)
|
||||
}
|
||||
return path.Join(catalog.SourceImageDir(), id.String()), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,13 +137,13 @@ func LbzSubmitListenHandler(store db.DB, mbzc mbz.MusicBrainzCaller) func(w http
|
|||
|
||||
artistMbzIDs, err := utils.ParseUUIDSlice(payload.TrackMeta.AdditionalInfo.ArtistMBIDs)
|
||||
if err != nil {
|
||||
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
|
||||
l.Debug().AnErr("error", err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
|
||||
}
|
||||
if len(artistMbzIDs) < 1 {
|
||||
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Attempting to parse artist UUIDs from mbid_mapping")
|
||||
l.Debug().AnErr("error", err).Msg("LbzSubmitListenHandler: Attempting to parse artist UUIDs from mbid_mapping")
|
||||
utils.ParseUUIDSlice(payload.TrackMeta.MBIDMapping.ArtistMBIDs)
|
||||
if err != nil {
|
||||
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
|
||||
l.Debug().AnErr("error", err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
|
||||
}
|
||||
}
|
||||
rgMbzID, err := uuid.Parse(payload.TrackMeta.AdditionalInfo.ReleaseGroupMBID)
|
||||
|
|
@ -191,7 +191,7 @@ func LbzSubmitListenHandler(store db.DB, mbzc mbz.MusicBrainzCaller) func(w http
|
|||
}
|
||||
mbid, err := uuid.Parse(a.ArtistMBID)
|
||||
if err != nil {
|
||||
l.Err(err).Msgf("LbzSubmitListenHandler: Failed to parse UUID for artist '%s'", a.ArtistName)
|
||||
l.Debug().AnErr("error", err).Msgf("LbzSubmitListenHandler: Failed to parse UUID for artist '%s'", a.ArtistName)
|
||||
}
|
||||
artistMbidMap = append(artistMbidMap, catalog.ArtistMbidMap{Artist: a.ArtistName, Mbid: mbid})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ func makeAuthRequest(t *testing.T, session, method, endpoint string, body io.Rea
|
|||
Name: "koito_session",
|
||||
Value: session,
|
||||
})
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
t.Logf("Making request to %s with session: %s", endpoint, session)
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
|
@ -512,7 +513,7 @@ func TestAuth(t *testing.T) {
|
|||
encoded = formdata.Encode()
|
||||
resp, err = http.DefaultClient.Post(host()+"/apis/web/v1/login", "application/x-www-form-urlencoded", strings.NewReader(encoded))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 400, resp.StatusCode)
|
||||
require.Equal(t, 401, resp.StatusCode)
|
||||
|
||||
// reset update so other tests dont fail
|
||||
req, err = http.NewRequest("PATCH", host()+fmt.Sprintf("/apis/web/v1/user?username=%s&password=%s", cfg.DefaultUsername(), cfg.DefaultPassword()), nil)
|
||||
|
|
@ -732,3 +733,160 @@ func TestAlbumReplaceImage(t *testing.T) {
|
|||
assert.NotNil(t, a.Image)
|
||||
assert.Equal(t, newid, *a.Image)
|
||||
}
|
||||
|
||||
func TestSetPrimaryArtist(t *testing.T) {
|
||||
|
||||
t.Run("Submit Listens", doSubmitListens)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// set and unset track primary artist
|
||||
|
||||
formdata := url.Values{}
|
||||
formdata.Set("artist_id", "1")
|
||||
formdata.Set("track_id", "1")
|
||||
formdata.Set("is_primary", "false")
|
||||
body := formdata.Encode()
|
||||
resp, err := makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
exists, err := store.RowExists(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM artist_tracks
|
||||
WHERE track_id = $1 AND artist_id = $2 AND is_primary = $3
|
||||
)`, 1, 1, false)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "expected artist is_primary to be false")
|
||||
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "1")
|
||||
formdata.Set("track_id", "1")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
exists, err = store.RowExists(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM artist_tracks
|
||||
WHERE track_id = $1 AND artist_id = $2 AND is_primary = $3
|
||||
)`, 1, 1, true)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "expected artist is_primary to be true")
|
||||
|
||||
// set and unset album primary artist
|
||||
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "1")
|
||||
formdata.Set("album_id", "1")
|
||||
formdata.Set("is_primary", "false")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
exists, err = store.RowExists(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM artist_releases
|
||||
WHERE release_id = $1 AND artist_id = $2 AND is_primary = $3
|
||||
)`, 1, 1, false)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "expected artist is_primary to be false")
|
||||
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "1")
|
||||
formdata.Set("album_id", "1")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
exists, err = store.RowExists(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM artist_releases
|
||||
WHERE release_id = $1 AND artist_id = $2 AND is_primary = $3
|
||||
)`, 1, 1, true)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "expected artist is_primary to be true")
|
||||
|
||||
// create a new track with multiple artists to make sure only one is primary at a time
|
||||
|
||||
listenBody := `{
|
||||
"listen_type": "single",
|
||||
"payload": [
|
||||
{
|
||||
"listened_at": 1749475719,
|
||||
"track_metadata": {
|
||||
"additional_info": {
|
||||
"artist_names": [
|
||||
"Rat Tally",
|
||||
"Madeline Kenney"
|
||||
],
|
||||
"duration_ms": 197270,
|
||||
"submission_client": "navidrome",
|
||||
"submission_client_version": "0.56.1 (fa2cf362)"
|
||||
},
|
||||
"artist_name": "Rat Tally feat. Madeline Kenney",
|
||||
"release_name": "In My Car",
|
||||
"track_name": "In My Car"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
req, err := http.NewRequest("POST", host()+"/apis/listenbrainz/1/submit-listens", strings.NewReader(listenBody))
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Token %s", apikey))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `{"status": "ok"}`, string(respBytes))
|
||||
|
||||
// set both artists as primary
|
||||
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "4")
|
||||
formdata.Set("album_id", "4")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "5")
|
||||
formdata.Set("album_id", "4")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "4")
|
||||
formdata.Set("track_id", "4")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
formdata = url.Values{}
|
||||
formdata.Set("artist_id", "5")
|
||||
formdata.Set("track_id", "4")
|
||||
formdata.Set("is_primary", "true")
|
||||
body = formdata.Encode()
|
||||
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 204, resp.StatusCode)
|
||||
|
||||
count, err := store.Count(ctx, `SELECT COUNT(*) FROM artist_releases WHERE release_id = $1 AND is_primary = $2`, 4, true)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count, "expected only one primary artist for release")
|
||||
count, err = store.Count(ctx, `SELECT COUNT(*) FROM artist_tracks WHERE track_id = $1 AND is_primary = $2`, 4, true)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count, "expected only one primary artist for track")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ func bindRoutes(
|
|||
|
||||
r.Route("/apis/web/v1", func(r chi.Router) {
|
||||
r.Get("/artist", handlers.GetArtistHandler(db))
|
||||
r.Get("/artists", handlers.GetArtistsForItemHandler(db))
|
||||
r.Get("/album", handlers.GetAlbumHandler(db))
|
||||
r.Get("/track", handlers.GetTrackHandler(db))
|
||||
r.Get("/top-tracks", handlers.GetTopTracksHandler(db))
|
||||
|
|
@ -75,6 +76,7 @@ func bindRoutes(
|
|||
r.Post("/merge/albums", handlers.MergeReleaseGroupsHandler(db))
|
||||
r.Post("/merge/artists", handlers.MergeArtistsHandler(db))
|
||||
r.Delete("/artist", handlers.DeleteArtistHandler(db))
|
||||
r.Post("/artists/primary", handlers.SetPrimaryArtistHandler(db))
|
||||
r.Delete("/album", handlers.DeleteAlbumHandler(db))
|
||||
r.Delete("/track", handlers.DeleteTrackHandler(db))
|
||||
r.Delete("/listen", handlers.DeleteListenHandler(db))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue