-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2c21403
Showing
7 changed files
with
529 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/gin-gonic/contrib/sessions" | ||
"github.com/gin-gonic/gin" | ||
"github.com/snabb/isoweek" | ||
"google.golang.org/api/fitness/v1" | ||
"google.golang.org/api/option" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
type stepsDBElement struct { | ||
string | ||
int64 | ||
} | ||
|
||
// getFitnessService returns the service object google fitness with apt permission to read steps | ||
func getFitnessService(user OAuthUser) (*fitness.Service, error) { | ||
|
||
tokenSource := config.TokenSource(context.TODO(), user.Token) | ||
service, err := fitness.NewService( | ||
context.TODO(), | ||
option.WithScopes(fitness.FitnessActivityReadScope), | ||
option.WithTokenSource(tokenSource), | ||
) | ||
if err != nil { | ||
err = fmt.Errorf("fitness api error: unable create service \"%v\"", err) | ||
} | ||
return service, err | ||
} | ||
|
||
// getStepCount returns a single int representing the numbers of steps between startTime and endTime | ||
func getStepCount(user OAuthUser, startTime time.Time, endTime time.Time) (int64, error) { | ||
service, err := getFitnessService(user) | ||
if err != nil { | ||
return -4, nil | ||
} | ||
|
||
// make a request to get steps | ||
stepsAggregateResult, err := service.Users.Dataset.Aggregate("me", &fitness.AggregateRequest{ | ||
AggregateBy: []*fitness.AggregateBy{ | ||
{ | ||
DataSourceId: "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps", | ||
DataTypeName: "com.google.step_count.delta", | ||
}, | ||
}, | ||
BucketByTime: &fitness.BucketByTime{ | ||
DurationMillis: endTime.Sub(startTime).Milliseconds(), | ||
}, | ||
EndTimeMillis: endTime.UnixNano() / nanosPerMilli, | ||
StartTimeMillis: startTime.UnixNano() / nanosPerMilli, | ||
}).Do() | ||
if err != nil { | ||
err = fmt.Errorf("fitness api error: unable to fetch required data due to \"%v\"", err) | ||
return -3, err | ||
} | ||
|
||
var steps int64 | ||
// extract the time of one bucket | ||
for _, bucket := range stepsAggregateResult.Bucket { | ||
for _, data := range bucket.Dataset { | ||
for _, point := range data.Point { | ||
for _, value := range point.Value { | ||
steps = value.IntVal | ||
goto endLoop | ||
} | ||
} | ||
} | ||
} | ||
|
||
endLoop: | ||
return steps, err | ||
} | ||
|
||
// getStepCountCurrentWeek returns step count for the current week | ||
func getStepCountCurrentWeek(user OAuthUser) (int64, error) { | ||
var currentYear, currentWeek = time.Now().In(timeLocation).ISOWeek() | ||
startOfWeek := isoweek.StartTime(currentYear, currentWeek, timeLocation) | ||
endOfWeek := startOfWeek.AddDate(0, 0, 7) | ||
return getStepCount(user, startOfWeek, endOfWeek) | ||
} | ||
|
||
// getStepCountCurrentWeek returns step count for the current day | ||
func getStepCountCurrentDay(user OAuthUser) (int64, error) { | ||
year, month, day := time.Now().In(timeLocation).Date() | ||
startOfDay := time.Date(year, month, day, 0, 0, 0, 0, timeLocation) | ||
endOfDay := startOfDay.AddDate(0, 0, 1) | ||
return getStepCount(user, startOfDay, endOfDay) | ||
} | ||
|
||
// geAllDetailsOfUser returns email-id, step count of the current week, step count of the current day | ||
// It's supposed to use all the functionality we have | ||
// TODO: update new functionality | ||
func geAllDetailsOfUser(user OAuthUser) (string, int64, int64, error) { | ||
|
||
currentWeekCount, err := getStepCountCurrentWeek(user) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
|
||
currentDayCount, err := getStepCountCurrentDay(user) | ||
if err != nil { | ||
return "", 0, 0, err | ||
} | ||
|
||
return user.Email, currentWeekCount, currentDayCount, err | ||
} | ||
|
||
// getStepCountWrapper puts the results of calling "getStepCountFunc" on "user" inside "resultQueue" channel | ||
// TODO: wg.Done() didn't work, why? | ||
// TODO: How to write this without using thread variable | ||
func getStepCountWrapper( | ||
resultQueue chan stepsDBElement, | ||
user OAuthUser, | ||
getStepCountFunc func(authUser OAuthUser) (int64, error)) { | ||
|
||
steps, err := getStepCountFunc(user) | ||
if err != nil { | ||
resultQueue <- stepsDBElement{user.Email, -1} | ||
return | ||
} | ||
|
||
resultQueue <- stepsDBElement{user.Email, steps} | ||
return | ||
} | ||
|
||
func getAll() (map[string]int64, error) { | ||
|
||
// get all users in usersChannels | ||
usersChannels := make(chan OAuthUser) | ||
go getUsersFromDB(usersChannels) | ||
|
||
// resultQueue : the steps of each user will be stored in it | ||
resultQueue := make(chan stepsDBElement) | ||
numbersOfUsers := 0 | ||
for user := range usersChannels { | ||
numbersOfUsers++ | ||
go getStepCountWrapper(resultQueue, user, getStepCountCurrentWeek) | ||
} | ||
|
||
result := make(map[string]int64) | ||
for i := 0; i < numbersOfUsers; i++ { | ||
userSteps := <-resultQueue | ||
result[userSteps.string] = userSteps.int64 | ||
} | ||
|
||
return result, nil | ||
} | ||
func list(ctx *gin.Context) { | ||
_ = sessions.Default(ctx) | ||
|
||
result, err := getAll() | ||
if err != nil { | ||
_ = ctx.AbortWithError(http.StatusInternalServerError, err) | ||
return | ||
} | ||
ctx.IndentedJSON(http.StatusOK, result) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module github.com/sedflix/fit | ||
|
||
go 1.15 | ||
|
||
require ( | ||
cloud.google.com/go v0.66.0 // indirect | ||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect | ||
github.com/coreos/go-oidc v2.2.1+incompatible | ||
github.com/gin-contrib/pprof v1.3.0 | ||
github.com/gin-gonic/contrib v0.0.0-20200913005814-1c32036e7ea4 | ||
github.com/gin-gonic/gin v1.6.3 | ||
github.com/gorilla/sessions v1.2.1 // indirect | ||
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect | ||
github.com/snabb/isoweek v1.0.0 | ||
go.mongodb.org/mongo-driver v1.4.1 | ||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 | ||
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect | ||
google.golang.org/api v0.31.0 | ||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"github.com/gin-gonic/contrib/sessions" | ||
"github.com/gin-gonic/gin" | ||
) | ||
|
||
func ErrorHandle() gin.HandlerFunc { | ||
return func(c *gin.Context) { | ||
c.Next() | ||
err := c.Errors.Last() | ||
if err == nil { | ||
return | ||
} | ||
|
||
c.JSON(c.Writer.Status(), | ||
gin.H{ | ||
"error": fmt.Sprintf("Error at the backend %v", err), | ||
}) | ||
return | ||
} | ||
} | ||
|
||
func main() { | ||
|
||
//setTimeZone | ||
err := setTimezone() | ||
if err != nil { | ||
return | ||
} | ||
|
||
// mongodb connection | ||
mongoURI := "mongodb://localhost:27017" | ||
err = setupMongo(mongoURI) | ||
if err != nil { | ||
return | ||
} | ||
|
||
// oauth connection | ||
err = setupOAuthClientCredentials("./credentials.json") | ||
if err != nil { | ||
return | ||
} | ||
|
||
router := gin.Default() | ||
|
||
// setup session cookie storage | ||
var store = sessions.NewCookieStore([]byte("secret")) | ||
router.Use(sessions.Sessions("goquestsession", store)) | ||
|
||
// custom error handling TODO: add ui | ||
router.Use(ErrorHandle()) | ||
|
||
// index page | ||
//router.Static("/css", "./static/css") | ||
//router.Static("/img", "./static/img") | ||
//router.LoadHTMLGlob("templates/*") | ||
|
||
router.GET("/list", list) | ||
router.GET("/login", authoriseUserHandler) | ||
router.GET("/auth", oAuthCallbackHandler) | ||
|
||
// Add the pprof routes | ||
//pprof.Register(router) | ||
|
||
_ = router.Run("127.0.0.1:9090") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/coreos/go-oidc" | ||
"go.mongodb.org/mongo-driver/bson" | ||
"go.mongodb.org/mongo-driver/mongo" | ||
"go.mongodb.org/mongo-driver/mongo/options" | ||
"golang.org/x/oauth2" | ||
"log" | ||
"time" | ||
) | ||
|
||
// oAuthUserCollection points to mongodb collection for storing our data | ||
var oAuthUserCollection *mongo.Collection | ||
|
||
// mongoClient is mongodb client | ||
var mongoClient *mongo.Client | ||
|
||
// OAuthUser stored in mongodb | ||
type OAuthUser struct { | ||
Email string `json:"email"` | ||
UserInfo *oidc.UserInfo | ||
Token *oauth2.Token | ||
} | ||
|
||
// setupMongo connects to the mongodb, creating database:users and collection:oauth | ||
// with index for email | ||
func setupMongo(mongoURI string) (err error) { | ||
|
||
mongoClient, err = mongo.NewClient(options.Client().ApplyURI(mongoURI)) | ||
if err != nil { | ||
log.Fatalf("unable to make monodb client with %s due to err %v", mongoURI, err) | ||
return err | ||
} | ||
|
||
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) | ||
err = mongoClient.Connect(ctx) | ||
if err != nil { | ||
log.Fatalf("unable to connect monodb at %s due to err %v", mongoURI, err) | ||
return err | ||
} | ||
|
||
// create database and collection | ||
oAuthUserCollection = mongoClient.Database("users").Collection("oauth") | ||
|
||
// create index for email | ||
_, err = oAuthUserCollection.Indexes().CreateOne(context.Background(), | ||
mongo.IndexModel{ | ||
Keys: bson.M{ | ||
"email": 1, | ||
}, | ||
Options: options.Index().SetUnique(true), | ||
}, | ||
) | ||
if err != nil { | ||
log.Fatalf("Unable to create index email due to %v", err) | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// addUserToDB will update the user record in the db or update if already present | ||
// record will be `upsert` to oauth package | ||
func addUserToDB(user OAuthUser) error { | ||
|
||
upsert := true | ||
updateResult, err := oAuthUserCollection.UpdateOne( | ||
context.TODO(), | ||
bson.M{"email": user.Email}, | ||
bson.M{"$set": user}, | ||
&options.UpdateOptions{Upsert: &upsert}, | ||
) | ||
if err != nil { | ||
return fmt.Errorf("unable to insert/update user %s in db due to %v", user.Email, err) | ||
} | ||
|
||
if updateResult.MatchedCount == 1 { | ||
log.Printf("UPDATED EXISTING USER: %s", user.Email) | ||
} | ||
if updateResult.UpsertedCount == 1 { | ||
log.Printf("INSERTED NEW USER: %s", user.Email) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getUsersFromDB get users from the database and and put it in usersChannels for consumption | ||
func getUsersFromDB(usersChannels chan OAuthUser) { | ||
defer close(usersChannels) | ||
|
||
findOptions := options.Find() | ||
cur, err := oAuthUserCollection.Find(context.TODO(), bson.D{{}}, findOptions) | ||
if err != nil { | ||
log.Printf("[ERROR]: Coun't get users from db due to %v\n", err) | ||
return | ||
} | ||
|
||
for cur.Next(context.TODO()) { | ||
var user OAuthUser | ||
err := cur.Decode(&user) | ||
if err != nil { | ||
log.Printf("[WARNING] unable to decode the output of user from mongodb due to %v", err) | ||
continue | ||
} | ||
usersChannels <- user | ||
} | ||
|
||
return | ||
|
||
} |
Oops, something went wrong.