Skip to content

Commit

Permalink
intial backend working version
Browse files Browse the repository at this point in the history
  • Loading branch information
sedflix committed Sep 20, 2020
0 parents commit 2c21403
Show file tree
Hide file tree
Showing 7 changed files with 529 additions and 0 deletions.
Empty file added README.md
Empty file.
161 changes: 161 additions & 0 deletions fitness.go
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)
}
20 changes: 20 additions & 0 deletions go.mod
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
)
68 changes: 68 additions & 0 deletions main.go
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")
}
112 changes: 112 additions & 0 deletions mongo.go
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

}
Loading

0 comments on commit 2c21403

Please sign in to comment.