-
Notifications
You must be signed in to change notification settings - Fork 1
[WIP] Feature/facebook login #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ | |
_obj | ||
_test | ||
|
||
conf.json | ||
|
||
# Architecture specific extensions/prefixes | ||
*.[568vq] | ||
[568vq].out | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package trip_repo | ||
|
||
import ( | ||
"github.com/devlucky/maporable-api/models" | ||
"errors" | ||
) | ||
|
||
type TripRepo interface { | ||
List() ([]*models.Trip) | ||
Get(id string) (*models.Trip) | ||
Create(trip *models.Trip) (error) | ||
} | ||
|
||
func Test() (TripRepo) { | ||
return NewInMemory() | ||
} | ||
|
||
|
||
type InMemory struct { | ||
trips map[string]*models.Trip | ||
} | ||
|
||
func NewInMemory() (*InMemory) { | ||
return &InMemory{ | ||
trips: make(map[string]*models.Trip), | ||
} | ||
} | ||
|
||
func (repo *InMemory) List() ([]*models.Trip) { | ||
list := make([]*models.Trip, 0, len(repo.trips)) | ||
|
||
for _, trip := range repo.trips { | ||
list = append(list, trip) | ||
} | ||
|
||
return list | ||
} | ||
|
||
func (repo *InMemory) Get(id string) (*models.Trip) { | ||
return repo.trips[id] | ||
} | ||
|
||
func (repo *InMemory) Create(trip *models.Trip) (error) { | ||
if _, ok := repo.trips[trip.Id]; ok { | ||
return errors.New("Duplicate ID for trip") | ||
} | ||
|
||
repo.trips[trip.Id] = trip | ||
return nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a new line missing at the end of some of your files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be I didn't even |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package api | ||
|
||
import ( | ||
"github.com/julienschmidt/httprouter" | ||
"net/http" | ||
"github.com/devlucky/maporable-api/models" | ||
"encoding/json" | ||
"log" | ||
"fmt" | ||
"github.com/devlucky/maporable-api/config" | ||
"net/url" | ||
"strings" | ||
"golang.org/x/oauth2" | ||
"io/ioutil" | ||
) | ||
|
||
func Login(w http.ResponseWriter, r *http.Request, ps httprouter.Params, a *config.Config) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand this endpoint. Could you detail it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our idea is to allow 3rd-party login via Facebook, right? This endpoint is used by the client to request the user to log in. It redirects the user to a Facebook login page with our app's information. When the user clicks on "accept" or "reject", Facebook calls the One of the main ideas of this is that in the future maybe we can parse the metadata of pictures to create an interactive map of where they've been and with whom. |
||
Url, err := url.Parse(a.FacebookOAuth.Endpoint.AuthURL) | ||
if err != nil { | ||
log.Fatal("Parse: ", err) | ||
} | ||
parameters := url.Values{} | ||
parameters.Add("client_id", a.FacebookOAuth.ClientID) | ||
parameters.Add("scope", strings.Join(a.FacebookOAuth.Scopes, " ")) | ||
parameters.Add("redirect_uri", a.FacebookOAuth.RedirectURL) | ||
parameters.Add("response_type", "code") | ||
parameters.Add("state", a.FacebookOAuthState) | ||
|
||
Url.RawQuery = parameters.Encode() | ||
http.Redirect(w, r, Url.String(), http.StatusTemporaryRedirect) | ||
} | ||
|
||
func LoginWithFacebook(w http.ResponseWriter, r *http.Request, ps httprouter.Params, a *config.Config) { | ||
state := r.FormValue("state") | ||
if state != a.FacebookOAuthState { | ||
log.Printf("invalid oauth state, expected '%s', got '%s'\n", a.FacebookOAuthState, state) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
code := r.FormValue("code") | ||
token, err := a.FacebookOAuth.Exchange(oauth2.NoContext, code) | ||
if err != nil { | ||
fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
resp, err := http.Get("https://graph.facebook.com/me?access_token=" + | ||
url.QueryEscape(token.AccessToken)) | ||
if err != nil { | ||
fmt.Printf("Get: %s\n", err) | ||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect) | ||
return | ||
} | ||
defer resp.Body.Close() | ||
|
||
response, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
fmt.Printf("ReadAll: %s\n", err) | ||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect) | ||
return | ||
} | ||
|
||
log.Printf("parseResponseBody: %s\n", string(response)) | ||
|
||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"_public_key": "e75de5fc546d36ee5aa8db5afaaccf4d81f85fd73f13fa42ac35755d58ec4474", | ||
"_fb_client_id": "1612792319017954", | ||
"fb_client_secret": "EJ[1:sNEwNSlfmiJCVksoGlqW9tr/IX4aYkVGzohgQ5fjjgA=:1ylVW6Jpt2rA1HjR3Vtv7QiO59zhW8wV:Xk+t9GlZ1puMfalx1NViafiXZ/6uDbvDpC03uaqdizzCcOC2QrMaCPwZKI4QB5YS]", | ||
"_fb_redirect_url": "localhost:8080" | ||
"_fb_state_string": "r6ctng3iyfhumowai73hrm" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package config | ||
|
||
import ( | ||
"github.com/devlucky/maporable-api/adapters/trip_repo" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
type Config struct { | ||
TripRepo trip_repo.TripRepo | ||
|
||
// Facebook OAuth | ||
FacebookOAuth *oauth2.Config | ||
FacebookOAuthState string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,17 +6,78 @@ import ( | |
"log" | ||
"github.com/julienschmidt/httprouter" | ||
"github.com/devlucky/maporable-api/api" | ||
"github.com/devlucky/maporable-api/adapters/trip_repo" | ||
"github.com/devlucky/maporable-api/config" | ||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/facebook" | ||
"encoding/json" | ||
"io/ioutil" | ||
) | ||
|
||
const userConfFilename string = "conf.json" | ||
|
||
// TODO: Move this to API | ||
func Ping(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | ||
fmt.Fprint(w, "pong") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice 😁 |
||
} | ||
|
||
func InjectConfig(a *config.Config, f func (http.ResponseWriter, *http.Request, httprouter.Params, *config.Config)) (func (http.ResponseWriter, *http.Request, httprouter.Params)) { | ||
return func (w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | ||
f(w, r, ps, a) | ||
} | ||
} | ||
|
||
type UserConfig struct { | ||
FbClientID string `json:"_fb_client_id"` | ||
FbClientSecret string `json:"fb_client_secret"` | ||
FbRedirectURL string `json:"_fb_redirect_url"` | ||
FbState string `json:"_fb_state "` | ||
} | ||
|
||
func GetConfigVars() (*UserConfig) { | ||
var uConf UserConfig | ||
conf, err := ioutil.ReadFile(userConfFilename) | ||
if err != nil { | ||
log.Fatalf("Error reading from file %s", userConfFilename) | ||
} | ||
|
||
err = json.Unmarshal(conf, uConf) | ||
if err != nil { | ||
log.Fatalf("Error unmarshaling conf in %s", userConfFilename) | ||
} | ||
|
||
return uConf | ||
} | ||
|
||
func CurrentConfig(uConf *UserConfig) (*config.Config) { | ||
return &config.Config{ | ||
TripRepo: trip_repo.NewInMemory(), | ||
FacebookOAuth: &oauth2.Config{ | ||
ClientID: uConf.FbClientID, | ||
ClientSecret: uConf.FbClientSecret, | ||
RedirectURL: uConf.FbRedirectURL, | ||
Scopes: []string{"public_profile"}, | ||
Endpoint: facebook.Endpoint, | ||
}, | ||
FacebookOAuthState: uCong.FbState, | ||
} | ||
} | ||
|
||
func main() { | ||
conf := CurrentConfig(GetConfigVars()) | ||
router := httprouter.New() | ||
|
||
// Ping-pong | ||
router.GET("/", Ping) | ||
|
||
router.POST("/trips", api.CreateTrip) | ||
// Authentication endpoints | ||
router.POST("/login", InjectConfig(conf, api.Login)) | ||
router.POST("/login/facebook", InjectConfig(conf, api.LoginWithFacebook)) | ||
|
||
// Trips endpoints | ||
router.GET("/trips", InjectConfig(conf, api.GetTripsList)) | ||
router.POST("/trips", InjectConfig(conf, api.CreateTrip)) | ||
router.GET("/trips/:id", InjectConfig(conf, api.GetTrip)) | ||
|
||
log.Println("Listening on 8080") | ||
log.Fatal(http.ListenAndServe(":8080", router)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,48 @@ | ||
package models | ||
|
||
import "errors" | ||
import ( | ||
"github.com/satori/go.uuid" | ||
) | ||
|
||
// A trip represents a one-time visit to a particular country | ||
type Trip struct { | ||
Id string `json:"id"` | ||
User string `json:"user"` | ||
Place string `json:"place"` | ||
Country string `json:"country"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. Everything you see here is very WIP at the moment. Our idea was to use an external API for countries, but I'm not sure yet which options we would have |
||
Status string `json:"status"` | ||
StartDate string `json:"start_date"` | ||
EndDate string `json:"end_date"` | ||
Latitude float64 `json:"latitude"` | ||
Longitude float64 `json:"longitude"` | ||
Description string `json:"description"` | ||
} | ||
|
||
func NewTrip(place string) (*Trip, error) { | ||
if place == "" { | ||
return nil, errors.New("The place cannot be empty") | ||
func NewTrip(user, country, status, startDate, endDate string) (*Trip, error) { | ||
err := validateDate(startDate) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = validateDate(endDate) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
trip := &Trip{ | ||
User: "hector", | ||
Place: place, | ||
Id: uuid.NewV4().String(), | ||
User: user, | ||
Country: country, | ||
Status: status, | ||
StartDate: startDate, | ||
EndDate: endDate, | ||
Latitude: 40.40, | ||
Longitude: 50.50, | ||
Description: "lots of sharks and groundhogs", | ||
} | ||
|
||
return trip, nil | ||
} | ||
|
||
// TODO: Actual functionality | ||
func getCoords(country string) (lat float64, long float64) { | ||
lat, long = 40.40, 50.50 | ||
return | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package models | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather move this into an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good :) |
||
|
||
import "time" | ||
|
||
func validateDate(d string) (error) { | ||
_, err := time.Parse(time.RFC3339, d) | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK it's not recommended to use
_
within package name, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. It's not very Go-y. What package structure and names would you use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following go coding style I'd say
triprepo