home to your local SPACEGIRL 馃挮 arimelody.space
at dev 198 lines 7.0 kB view raw
1package api 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "os" 8 "strings" 9 10 "arimelody-web/controller" 11 "arimelody-web/model" 12) 13 14func Handler(app *model.AppState) http.Handler { 15 mux := http.NewServeMux() 16 17 // TODO: generate API keys on the frontend 18 19 // ARTIST ENDPOINTS 20 21 mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 var artistID = strings.Split(r.URL.Path[1:], "/")[0] 23 artist, err := controller.GetArtist(app.DB, artistID) 24 if err != nil { 25 if strings.Contains(err.Error(), "no rows") { 26 http.NotFound(w, r) 27 return 28 } 29 fmt.Printf("WARN: Error while retrieving artist %s: %s\n", artistID, err) 30 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 31 return 32 } 33 34 switch r.Method { 35 case http.MethodGet: 36 // GET /api/v1/artist/{id} 37 ServeArtist(app, artist).ServeHTTP(w, r) 38 case http.MethodPut: 39 // PUT /api/v1/artist/{id} (admin) 40 requireAccount(UpdateArtist(app, artist)).ServeHTTP(w, r) 41 case http.MethodDelete: 42 // DELETE /api/v1/artist/{id} (admin) 43 requireAccount(DeleteArtist(app, artist)).ServeHTTP(w, r) 44 default: 45 http.NotFound(w, r) 46 } 47 }))) 48 mux.Handle("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 switch r.Method { 50 case http.MethodGet: 51 // GET /api/v1/artist 52 ServeAllArtists(app).ServeHTTP(w, r) 53 case http.MethodPost: 54 // POST /api/v1/artist (admin) 55 requireAccount(CreateArtist(app)).ServeHTTP(w, r) 56 default: 57 http.NotFound(w, r) 58 } 59 })) 60 61 // RELEASE ENDPOINTS 62 63 mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 var releaseID = strings.Split(r.URL.Path[1:], "/")[0] 65 release, err := controller.GetRelease(app.DB, releaseID, true) 66 if err != nil { 67 if strings.Contains(err.Error(), "no rows") { 68 http.NotFound(w, r) 69 return 70 } 71 fmt.Printf("WARN: Error while retrieving release %s: %s\n", releaseID, err) 72 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 73 return 74 } 75 76 switch r.Method { 77 case http.MethodGet: 78 // GET /api/v1/music/{id} 79 ServeRelease(app, release).ServeHTTP(w, r) 80 case http.MethodPut: 81 // PUT /api/v1/music/{id} (admin) 82 requireAccount(UpdateRelease(app, release)).ServeHTTP(w, r) 83 case http.MethodDelete: 84 // DELETE /api/v1/music/{id} (admin) 85 requireAccount(DeleteRelease(app, release)).ServeHTTP(w, r) 86 default: 87 http.NotFound(w, r) 88 } 89 }))) 90 mux.Handle("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 91 switch r.Method { 92 case http.MethodGet: 93 // GET /api/v1/music 94 ServeCatalog(app).ServeHTTP(w, r) 95 case http.MethodPost: 96 // POST /api/v1/music (admin) 97 requireAccount(CreateRelease(app)).ServeHTTP(w, r) 98 default: 99 http.NotFound(w, r) 100 } 101 })) 102 103 // TRACK ENDPOINTS 104 105 mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 106 var trackID = strings.Split(r.URL.Path[1:], "/")[0] 107 track, err := controller.GetTrack(app.DB, trackID) 108 if err != nil { 109 if strings.Contains(err.Error(), "no rows") { 110 http.NotFound(w, r) 111 return 112 } 113 fmt.Printf("WARN: Error while retrieving track %s: %s\n", trackID, err) 114 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 115 return 116 } 117 118 switch r.Method { 119 case http.MethodGet: 120 // GET /api/v1/track/{id} (admin) 121 requireAccount(ServeTrack(app, track)).ServeHTTP(w, r) 122 case http.MethodPut: 123 // PUT /api/v1/track/{id} (admin) 124 requireAccount(UpdateTrack(app, track)).ServeHTTP(w, r) 125 case http.MethodDelete: 126 // DELETE /api/v1/track/{id} (admin) 127 requireAccount(DeleteTrack(app, track)).ServeHTTP(w, r) 128 default: 129 http.NotFound(w, r) 130 } 131 }))) 132 mux.Handle("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 133 switch r.Method { 134 case http.MethodGet: 135 // GET /api/v1/track (admin) 136 requireAccount(ServeAllTracks(app)).ServeHTTP(w, r) 137 case http.MethodPost: 138 // POST /api/v1/track (admin) 139 requireAccount(CreateTrack(app)).ServeHTTP(w, r) 140 default: 141 http.NotFound(w, r) 142 } 143 })) 144 145 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 146 session, err := getSession(app, r) 147 if err != nil { 148 fmt.Fprintf(os.Stderr, "WARN: Failed to get session: %v\n", err) 149 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 150 return 151 } 152 ctx := context.WithValue(r.Context(), "session", session) 153 mux.ServeHTTP(w, r.WithContext(ctx)) 154 }) 155} 156 157func requireAccount(next http.Handler) http.Handler { 158 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 159 session := r.Context().Value("session").(*model.Session) 160 if session == nil || session.Account == nil { 161 http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 162 return 163 } 164 ctx := context.WithValue(r.Context(), "session", session) 165 next.ServeHTTP(w, r.WithContext(ctx)) 166 }) 167} 168 169func getSession(app *model.AppState, r *http.Request) (*model.Session, error) { 170 var token string 171 172 // check cookies first 173 sessionCookie, err := r.Cookie(model.COOKIE_TOKEN) 174 if err != nil && err != http.ErrNoCookie { 175 return nil, fmt.Errorf("Failed to retrieve session cookie: %v\n", err) 176 } 177 if sessionCookie != nil { 178 token = sessionCookie.Value 179 } else { 180 // check Authorization header 181 token = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") 182 } 183 184 if token == "" { return nil, nil } 185 186 // fetch existing session 187 session, err := controller.GetSession(app.DB, token) 188 189 if err != nil && !strings.Contains(err.Error(), "no rows") { 190 return nil, fmt.Errorf("Failed to retrieve session: %v\n", err) 191 } 192 193 if session != nil { 194 // TODO: consider running security checks here (i.e. user agent mismatches) 195 } 196 197 return session, nil 198}