package api import ( "context" "fmt" "net/http" "os" "strings" "arimelody-web/controller" "arimelody-web/model" ) func Handler(app *model.AppState) http.Handler { mux := http.NewServeMux() // TODO: generate API keys on the frontend // ARTIST ENDPOINTS mux.Handle("GET /v1/artist/{id}", ServeArtist(app)) mux.Handle("PUT /v1/artist/{id}", requireAccount(UpdateArtist(app))) mux.Handle("DELETE /v1/artist/{id}", requireAccount(DeleteArtist(app))) mux.Handle("GET /v1/artist/", ServeAllArtists(app)) mux.Handle("GET /v1/artist", ServeAllArtists(app)) mux.Handle("POST /v1/artist/", requireAccount(CreateArtist(app))) mux.Handle("POST /v1/artist", requireAccount(CreateArtist(app))) // RELEASE ENDPOINTS mux.Handle("GET /v1/music/{id}", ServeRelease(app)) mux.Handle("PUT /v1/music/{id}", requireAccount(UpdateRelease(app))) mux.Handle("DELETE /v1/music/{id}", requireAccount(DeleteRelease(app))) mux.Handle("PUT /v1/music/{id}/tracks", requireAccount(UpdateReleaseTracks(app))) mux.Handle("PUT /v1/music/{id}/credits", requireAccount(UpdateReleaseCredits(app))) mux.Handle("PUT /v1/music/{id}/links", requireAccount(UpdateReleaseLinks(app))) mux.Handle("GET /v1/music/", ServeCatalog(app)) mux.Handle("GET /v1/music", ServeCatalog(app)) mux.Handle("POST /v1/music/", requireAccount(CreateRelease(app))) mux.Handle("POST /v1/music", requireAccount(CreateRelease(app))) // TRACK ENDPOINTS mux.Handle("GET /v1/track/{id}", requireAccount(ServeTrack(app))) mux.Handle("PUT /v1/track/{id}", requireAccount(UpdateTrack(app))) mux.Handle("DELETE /v1/track/{id}", requireAccount(DeleteTrack(app))) mux.Handle("GET /v1/track/", requireAccount(ServeAllTracks(app))) mux.Handle("GET /v1/track", requireAccount(ServeAllTracks(app))) mux.Handle("POST /v1/track/", requireAccount(CreateTrack(app))) mux.Handle("POST /v1/track", requireAccount(CreateTrack(app))) // BLOG ENDPOINTS mux.Handle("GET /v1/blog/{id}", ServeBlog(app)) mux.Handle("PUT /v1/blog/{id}", requireAccount(UpdateBlog(app))) mux.Handle("DELETE /v1/blog/{id}", requireAccount(DeleteBlog(app))) mux.Handle("GET /v1/blog/", ServeAllBlogs(app)) mux.Handle("GET /v1/blog", ServeAllBlogs(app)) mux.Handle("POST /v1/blog/", requireAccount(CreateBlog(app))) mux.Handle("POST /v1/blog", requireAccount(CreateBlog(app))) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session, err := getSession(app, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to get session: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } ctx := context.WithValue(r.Context(), "session", session) mux.ServeHTTP(w, r.WithContext(ctx)) }) } func requireAccount(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) if session == nil || session.Account == nil { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), "session", session) next.ServeHTTP(w, r.WithContext(ctx)) }) } func getSession(app *model.AppState, r *http.Request) (*model.Session, error) { var token string // check cookies first sessionCookie, err := r.Cookie(model.COOKIE_TOKEN) if err != nil && err != http.ErrNoCookie { return nil, fmt.Errorf("Failed to retrieve session cookie: %v\n", err) } if sessionCookie != nil { token = sessionCookie.Value } else { // check Authorization header token = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") } if token == "" { return nil, nil } // fetch existing session session, err := controller.GetSession(app.DB, token) if err != nil && !strings.Contains(err.Error(), "no rows") { return nil, fmt.Errorf("Failed to retrieve session: %v\n", err) } if session != nil { // TODO: consider running security checks here (i.e. user agent mismatches) } return session, nil }