github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/importer/foursquare/foursquare.go (about)

     1  /*
     2  Copyright 2014 The Camlistore Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package foursquare implements an importer for foursquare.com accounts.
    18  package foursquare
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"log"
    25  	"net/http"
    26  	"strings"
    27  	"sync"
    28  
    29  	"camlistore.org/pkg/httputil"
    30  	"camlistore.org/pkg/importer"
    31  	"camlistore.org/pkg/jsonconfig"
    32  	"camlistore.org/third_party/code.google.com/p/goauth2/oauth"
    33  )
    34  
    35  func init() {
    36  	importer.Register("foursquare", newFromConfig)
    37  }
    38  
    39  type imp struct {
    40  	host *importer.Host
    41  
    42  	oauthConfig *oauth.Config
    43  	tokenCache  oauth.Cache
    44  
    45  	mu   sync.Mutex
    46  	user string
    47  }
    48  
    49  func newFromConfig(cfg jsonconfig.Obj, host *importer.Host) (importer.Importer, error) {
    50  	apiKey := cfg.RequiredString("apiKey")
    51  	if err := cfg.Validate(); err != nil {
    52  		return nil, err
    53  	}
    54  	parts := strings.Split(apiKey, ":")
    55  	if len(parts) != 2 {
    56  		return nil, fmt.Errorf("Foursquare importer: Invalid apiKey configuration: %q", apiKey)
    57  	}
    58  	clientID, clientSecret := parts[0], parts[1]
    59  	im := &imp{
    60  		host:       host,
    61  		tokenCache: &tokenCache{},
    62  		oauthConfig: &oauth.Config{
    63  			ClientId:     clientID,
    64  			ClientSecret: clientSecret,
    65  			AuthURL:      "https://foursquare.com/oauth2/authenticate",
    66  			TokenURL:     "https://foursquare.com/oauth2/access_token",
    67  			RedirectURL:  host.BaseURL + "callback",
    68  		},
    69  	}
    70  	// TODO: schedule work?
    71  	return im, nil
    72  }
    73  
    74  type tokenCache struct {
    75  	mu    sync.Mutex
    76  	token *oauth.Token
    77  }
    78  
    79  func (tc *tokenCache) Token() (*oauth.Token, error) {
    80  	tc.mu.Lock()
    81  	defer tc.mu.Unlock()
    82  	if tc.token == nil {
    83  		return nil, errors.New("no token")
    84  	}
    85  	return tc.token, nil
    86  }
    87  
    88  func (tc *tokenCache) PutToken(t *oauth.Token) error {
    89  	tc.mu.Lock()
    90  	defer tc.mu.Unlock()
    91  	tc.token = t
    92  	return nil
    93  
    94  }
    95  func (im *imp) CanHandleURL(url string) bool { return false }
    96  func (im *imp) ImportURL(url string) error   { panic("unused") }
    97  
    98  func (im *imp) Prefix() string {
    99  	im.mu.Lock()
   100  	defer im.mu.Unlock()
   101  	if im.user == "" {
   102  		// This should only get called when we're importing, but check anyway.
   103  		panic("Prefix called before authenticated")
   104  	}
   105  	return fmt.Sprintf("foursquare:%s", im.user)
   106  }
   107  
   108  func (im *imp) String() string {
   109  	im.mu.Lock()
   110  	defer im.mu.Unlock()
   111  	userId := "<unauthenticated>"
   112  	if im.user != "" {
   113  		userId = im.user
   114  	}
   115  	return fmt.Sprintf("foursquare:%s", userId)
   116  }
   117  
   118  func (im *imp) Run(intr importer.Interrupt) error {
   119  	token, err := im.tokenCache.Token()
   120  	if err != nil {
   121  		return fmt.Errorf("Foursquare importer can't run. Token error: %v", err)
   122  	}
   123  
   124  	res, err := im.host.HTTPClient().Get("https://api.foursquare.com/v2/users/self?oauth_token=" + token.AccessToken)
   125  	if err != nil {
   126  		log.Printf("Error fetching //api.foursquare.com/v2/users/self: %v", err)
   127  		return err
   128  	}
   129  	all, _ := ioutil.ReadAll(res.Body)
   130  	res.Body.Close()
   131  	log.Printf("Got: %s", all)
   132  	return nil
   133  }
   134  
   135  func (im *imp) getRootNode() (*importer.Object, error) {
   136  	root, err := im.host.RootObject()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	if root.Attr("title") == "" {
   142  		im.mu.Lock()
   143  		user := im.user
   144  		im.mu.Unlock()
   145  
   146  		title := fmt.Sprintf("Foursquare (%s)", user)
   147  		if err := root.SetAttr("title", title); err != nil {
   148  			return nil, err
   149  		}
   150  	}
   151  	return root, nil
   152  }
   153  
   154  func (im *imp) serveLogin(w http.ResponseWriter, r *http.Request) {
   155  	state := "no_clue_what_this_is" // TODO: ask adg to document this. or send him a CL.
   156  	authURL := im.oauthConfig.AuthCodeURL(state)
   157  	http.Redirect(w, r, authURL, 302)
   158  }
   159  
   160  func (im *imp) serveCallback(w http.ResponseWriter, r *http.Request) {
   161  	if r.Method != "GET" {
   162  		http.Error(w, "Expected a GET", 400)
   163  		return
   164  	}
   165  	code := r.FormValue("code")
   166  	if code == "" {
   167  		http.Error(w, "Expected a code", 400)
   168  		return
   169  	}
   170  	transport := &oauth.Transport{Config: im.oauthConfig}
   171  	token, err := transport.Exchange(code)
   172  	log.Printf("Token = %#v, error %v", token, err)
   173  	if err != nil {
   174  		log.Printf("Token Exchange error: %v", err)
   175  		http.Error(w, "token exchange error", 500)
   176  		return
   177  	}
   178  	im.tokenCache.PutToken(token)
   179  	http.Redirect(w, r, im.host.BaseURL+"?mode=start", 302)
   180  }
   181  
   182  func (im *imp) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   183  	if strings.HasSuffix(r.URL.Path, "/login") {
   184  		im.serveLogin(w, r)
   185  	} else if strings.HasSuffix(r.URL.Path, "/callback") {
   186  		im.serveCallback(w, r)
   187  	} else {
   188  		httputil.BadRequestError(w, "Unknown path: %s", r.URL.Path)
   189  	}
   190  }