Api.elm (5769B)
1 module Api exposing (fetchListens, fetchSectionData, fetchSimilarTracks, fetchStats, httpErrorToString, listensDecoder, sectionEntriesDecoder, similarTracksDecoder, statsDecoder, statsUrl) 2 3 import Http 4 import Json.Decode as D exposing (Decoder) 5 import Types exposing (ActiveFilter, Listen, Msg(..), Period(..), SimilarTrack, Stats, StatsEntry) 6 import Url 7 8 9 fetchListens : String -> Int -> Int -> Maybe ActiveFilter -> Maybe String -> Cmd Msg 10 fetchListens userSlug limit offset mFilter mSearch = 11 let 12 filterParams = 13 case mFilter of 14 Nothing -> 15 "" 16 17 Just { field, value } -> 18 "&filterField=" ++ field ++ "&filterValue=" ++ Url.percentEncode value 19 20 searchParam = 21 case mSearch of 22 Nothing -> 23 "" 24 25 Just q -> 26 "&search=" ++ Url.percentEncode q 27 28 url = 29 "/proxy?user=" ++ userSlug ++ "&limit=" ++ String.fromInt limit ++ "&offset=" ++ String.fromInt offset ++ filterParams ++ searchParam 30 in 31 Http.get 32 { url = url 33 , expect = Http.expectJson GotListens listensDecoder 34 } 35 36 37 fetchStats : String -> Period -> Cmd Msg 38 fetchStats userSlug period = 39 Http.get 40 { url = statsUrl userSlug period Nothing 41 , expect = Http.expectJson GotStats statsDecoder 42 } 43 44 45 fetchSectionData : String -> Period -> String -> Cmd Msg 46 fetchSectionData userSlug period section = 47 Http.get 48 { url = statsUrl userSlug period (Just section) 49 , expect = Http.expectJson (GotSectionData section) (sectionEntriesDecoder section) 50 } 51 52 53 statsUrl : String -> Period -> Maybe String -> String 54 statsUrl userSlug period mSection = 55 let 56 periodPart = 57 case period of 58 AllTime -> 59 "" 60 61 LastDays n -> 62 "&period=" ++ String.fromInt n 63 64 CustomRange from to -> 65 "&from=" ++ from ++ "&to=" ++ to 66 67 sectionPart = 68 case mSection of 69 Nothing -> 70 "" 71 72 Just sec -> 73 "§ion=" ++ sec 74 in 75 "/stats?user=" ++ userSlug ++ periodPart ++ sectionPart 76 77 78 fetchSimilarTracks : Int -> String -> String -> Cmd Msg 79 fetchSimilarTracks idx artist track = 80 Http.get 81 { url = "/similar?artist=" ++ Url.percentEncode artist ++ "&track=" ++ Url.percentEncode track 82 , expect = Http.expectJson (FetchSimilarTracks idx artist track) similarTracksDecoder 83 } 84 85 86 httpErrorToString : Http.Error -> String 87 httpErrorToString err = 88 case err of 89 Http.BadUrl url -> 90 "Bad URL: " ++ url 91 92 Http.Timeout -> 93 "Request timed out" 94 95 Http.NetworkError -> 96 "Network error" 97 98 Http.BadStatus status -> 99 "Server error: " ++ String.fromInt status 100 101 Http.BadBody body -> 102 "JSON decode error: " ++ body 103 104 105 106 -- DECODERS 107 108 109 listensDecoder : Decoder (List Listen) 110 listensDecoder = 111 D.at [ "payload", "listens" ] (D.list listenDecoder) 112 113 114 listenDecoder : Decoder Listen 115 listenDecoder = 116 D.map2 117 (\meta listenedAt -> 118 { trackName = meta.trackName 119 , artistName = meta.artistName 120 , releaseName = meta.releaseName 121 , listenedAt = listenedAt 122 , genre = meta.genre 123 , label = meta.label 124 , releaseMbid = meta.releaseMbid 125 , caaReleaseMbid = meta.caaReleaseMbid 126 } 127 ) 128 (D.field "track_metadata" trackMetaDecoder) 129 (D.maybe (D.field "listened_at" D.int)) 130 131 132 type alias TrackMeta = 133 { trackName : Maybe String 134 , artistName : Maybe String 135 , releaseName : Maybe String 136 , genre : Maybe String 137 , releaseMbid : Maybe String 138 , caaReleaseMbid : Maybe String 139 , label : Maybe String 140 } 141 142 143 trackMetaDecoder : Decoder TrackMeta 144 trackMetaDecoder = 145 D.map7 TrackMeta 146 (D.maybe (D.field "track_name" D.string)) 147 (D.maybe (D.field "artist_name" D.string)) 148 (D.maybe (D.field "release_name" D.string)) 149 (D.maybe (D.field "genre" D.string)) 150 (D.maybe (D.at [ "mbid_mapping", "release_mbid" ] D.string)) 151 (D.maybe (D.at [ "mbid_mapping", "caa_release_mbid" ] D.string)) 152 (D.maybe (D.field "label" D.string)) 153 154 155 statsDecoder : Decoder Stats 156 statsDecoder = 157 D.map5 Stats 158 (D.field "genres" (D.list entryDecoder)) 159 (D.field "labels" (D.list entryDecoder)) 160 (D.field "years" (D.list entryDecoder)) 161 (D.field "artists" (D.list entryDecoder)) 162 (D.field "tracks" (D.list entryDecoder)) 163 164 165 entryDecoder : Decoder StatsEntry 166 entryDecoder = 167 D.map2 StatsEntry 168 (D.field "name" D.string) 169 (D.field "count" D.int) 170 171 172 sectionEntriesDecoder : String -> Decoder (List StatsEntry) 173 sectionEntriesDecoder section = 174 D.map 175 (\stats -> 176 case section of 177 "artist" -> 178 stats.artists 179 180 "track" -> 181 stats.tracks 182 183 "genre" -> 184 stats.genres 185 186 "label" -> 187 stats.labels 188 189 "year" -> 190 stats.years 191 192 _ -> 193 [] 194 ) 195 statsDecoder 196 197 198 similarTracksDecoder : Decoder (List SimilarTrack) 199 similarTracksDecoder = 200 D.at [ "data", "similar_tracks" ] (D.list similarTrackDecoder) 201 202 203 similarTrackDecoder : Decoder SimilarTrack 204 similarTrackDecoder = 205 D.map4 SimilarTrack 206 (D.field "artist" D.string) 207 (D.field "track" D.string) 208 (D.maybe (D.field "score" D.float)) 209 (D.maybe (D.field "video_uri" D.string))