Skip to content
Permalink
Browse files

Add some GraphQL subscription support #32

  • Loading branch information...
upvalue committed Jan 29, 2019
1 parent 3d7a1cd commit fabbf2d4e2cc5eb7db7052574ba29c2c0757e84b
@@ -185,13 +185,7 @@ func App() *macaron.Macaron {
return m
}

// Server returns a server that closes gracefully
func Server() *http.Server {
return &http.Server{
Addr: fmt.Sprintf("%s:%v", Config.Host, Config.Port),
Handler: App(),
}
}
var app = App()

// Main is the entry point for meditations; it handles CLI options and starts
func Main() {
@@ -313,6 +307,8 @@ func Main() {

DBOpen()

graphqlInitialize()

if Config.Demo {
// ticker := time.NewTicker(time.Second * 10)
ticker := time.NewTicker(time.Hour)
@@ -333,7 +329,12 @@ func Main() {
// DBSeed
log.Printf("running with configuration %+v\n", Config)
log.Printf("starting server")
server := Server()

server := &http.Server{
Addr: fmt.Sprintf("%s:%v", Config.Host, Config.Port),
// Handler: app,
}

err := server.ListenAndServe()
log.Printf("%v", err)
},
@@ -1,16 +1,26 @@
// graphql.go - GraphQL resolvers
// gaphql.go - GraphQL resolvers
package backend

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/functionalfoundry/graphqlws"

"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
macaron "gopkg.in/macaron.v1"
)

type contextKey string

func (c contextKey) String() string {
return "meditations.gqlvalue"
}

var taskInterface = graphql.NewObject(graphql.ObjectConfig{
Name: "Task",
Description: "Task",
@@ -19,10 +29,24 @@ var taskInterface = graphql.NewObject(graphql.ObjectConfig{
Type: graphql.NewNonNull(graphql.Int),
Description: "ID",
},

"Name": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "Name",
},

"CreatedAt": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "CreatedAt",
},
"UpdatedAt": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
Description: "CreatedAt",
},
"DeletedAt": &graphql.Field{
Type: graphql.String,
Description: "DeletedAt",
},
"Minutes": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
Description: "Minutes spent on task as an integer",
@@ -32,7 +56,7 @@ var taskInterface = graphql.NewObject(graphql.ObjectConfig{
Description: "Date of the task, determining its scope",
},

"Order": &graphql.Field{
"Position": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
Description: "Task order within scope",
},
@@ -138,6 +162,8 @@ var queryType = graphql.NewObject(graphql.ObjectConfig{
},

// Tasks by date query. Pulls tasks for particular scopes given a YYYY-MM-DD date.

// tasksByDate(date: String!, scopes: [DateScope!])
"tasksByDate": &graphql.Field{
Type: graphql.NewNonNull(dateScopeReturn),
Args: graphql.FieldConfigArgument{
@@ -250,6 +276,21 @@ var queryType = graphql.NewObject(graphql.ObjectConfig{

///// MUTATIONS

/*
type TaskInput {
ID: Int
Name: String
Minutes: Int
MinutesDelta: Int
Scope: Int
Status: Int
Comment: String
CompletedTasks: Int
TotalTasks: Int
CompletionRate: Int
}
*/

var taskInputObject = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "InputTask",
Fields: graphql.InputObjectConfigFieldMap{
@@ -316,6 +357,15 @@ var mutationType = graphql.NewObject(graphql.ObjectConfig{

DB.Save(&task)

var tasks []Task

tasks = append(tasks, task)

graphqlPush("taskEvents", habitSyncMsg{
Tasks: tasks,
ProjectID: 0,
})

return task, nil
},
},
@@ -426,6 +476,79 @@ var mutationType = graphql.NewObject(graphql.ObjectConfig{
},
})

var thingType1 = graphql.NewObject(graphql.ObjectConfig{
Name: "ThingType1",
Fields: graphql.Fields{
"Message": {
Type: graphql.String,
},
},
})

var habitSyncMsgType = graphql.NewObject(graphql.ObjectConfig{
Name: "habitSyncMessage",
Fields: graphql.Fields{
"Tasks": {
Type: graphql.NewList(taskInterface),
},
"ProjectID": {
Type: graphql.Int,
},
},
})

var taskEventDataType = graphql.NewUnion(graphql.UnionConfig{
Name: "TaskEventData",
Types: []*graphql.Object{
habitSyncMsgType,
},
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
return habitSyncMsgType
},
})

var taskEventType = graphql.NewObject(graphql.ObjectConfig{
Name: "TaskEvent",
Fields: graphql.Fields{
"Type": &graphql.Field{
Type: graphql.NewNonNull(graphql.String),
},
"Data": &graphql.Field{
Type: taskEventDataType,
},
},
})

var n = 0

type TaskEvent struct {
Type string
Data interface{}
}

var subscriptionType = graphql.NewObject(graphql.ObjectConfig{
Name: "Subscription",
Fields: graphql.Fields{
"taskEvents": &graphql.Field{
Type: taskEventType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {

iface := p.Context.Value("meditations.gqlvalue")

taskEvent := TaskEvent{
Type: "UPDATE_TASKS_AND_PROJECT",
Data: iface,
}

f, _ := json.Marshal(taskEvent)
fmt.Printf("%+v\n", string(f))

return taskEvent, nil
},
},
},
})

func executeVarQuery(query string, vars map[string]interface{}) *graphql.Result {
result := graphql.Do(graphql.Params{
Schema: schema,
@@ -446,14 +569,101 @@ func executeQuery(query string) *graphql.Result {
var schema graphql.Schema
var graphqlinitialized = false

var subscriptionManager graphqlws.SubscriptionManager

// graphqlPush pushes subscription updates
func graphqlPush(name string, data interface{}) {
subs := subscriptionManager.Subscriptions()

fmt.Printf("GraphQL: Pushing %s to %d subscribers\n", name, len(subs))

for conn, _ := range subs {
ctx := context.WithValue(context.TODO(), "meditations.gqlvalue", data)

for _, sub := range subs[conn] {
fmt.Printf("sending data %+v\n", data)

if sub.Fields[0] == name {
params := graphql.Params{
Schema: schema,
RequestString: sub.Query,
VariableValues: sub.Variables,
OperationName: sub.OperationName,
Context: ctx,
}

result := graphql.Do(params)

sub.SendData(&graphqlws.DataMessagePayload{
Data: result.Data,
Errors: graphqlws.ErrorsFromGraphQLErrors(result.Errors),
})

}
}
//result := graphql.Do
}

}

func graphqlInitialize() {
if graphqlinitialized == false {
fmt.Printf("GraphQL Schema initialized\n")
schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: queryType,
Mutation: mutationType,
Query: queryType,
Mutation: mutationType,
Subscription: subscriptionType,
})
graphqlinitialized = true

subscriptionManager = graphqlws.NewSubscriptionManager(&schema)

graphqlwsHandler := graphqlws.NewHandler(graphqlws.HandlerConfig{
SubscriptionManager: subscriptionManager,
})

h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
})

http.Handle("/graphql", h)
http.Handle("/subscriptions", graphqlwsHandler)

/*
ticker := time.NewTicker(5 * time.Second)
go func() {
for {
select {
case <-ticker.C:
n += 1
subscriptions := subscriptionManager.Subscriptions()
for conn := range subscriptions {
for _, sub := range subscriptions[conn] {
params := graphql.Params{
Schema: schema,
RequestString: sub.Query,
VariableValues: sub.Variables,
OperationName: sub.OperationName,
}
// fmt.Printf("%+v\n", sub.Fields[0])
result := graphql.Do(params)
data := graphqlws.DataMessagePayload{
Data: result.Data,
Errors: graphqlws.ErrorsFromGraphQLErrors(result.Errors),
}
sub.SendData(&data)
}
}
}
}
}()
*/
}
}

@@ -336,9 +336,15 @@ func (task *Task) SyncWithStats(includeMainTask bool) {
tasks = append(tasks, *task)
}

// It is possible for this to result in zero tasks to send, if a task has been deleted and
// no stat recalculations are necessary
if len(tasks) != 0 {
// It is possible for this to result in zero tasks to send, if a task has been deleted and
// no stat recalculations are necessary
fmt.Printf("Calling GRAPHQL PUSH!\n")
graphqlPush("UPDATE_TASKS_AND_PROJECT", habitSyncMsg{
Tasks: tasks,
ProjectID: project.ID,
})

habitSync.Send("UPDATE_TASKS_AND_PROJECT", habitSyncMsg{
Tasks: tasks,
ProjectID: project.ID,
@@ -0,0 +1,2 @@
// subscriptions.go - GraphQL subscriptions implementation
package backend
@@ -5,6 +5,7 @@
"dependencies": {
"@reach/router": "^1.2.1",
"@types/classnames": "^2.2.6",
"@types/lodash-es": "^4.17.1",
"@types/reach__router": "^1.2.2",
"@types/react": "^16.7.6",
"@types/react-beautiful-dnd": "^7.1.2",
@@ -14,6 +15,7 @@
"@upvalueio/third-coast": "^0.1.17",
"classnames": "^2.2.6",
"date-fns": "^2.0.0-alpha.25",
"lodash-es": "^4.17.11",
"react": "^16.7.0-alpha.2",
"react-beautiful-dnd": "^10.0.1",
"react-dev-utils": "^6.0.6-next.c662dfb0",
@@ -12,7 +12,7 @@
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="loader"><span class="s1">\</span>o<span class="s2">/</span> meditations loading</div>
<div id="loader"><span class="s1">\</span>o<span class="s2">/</span> Meditations is loading.</div>
<script type="text/javascript">
var i = 0, cycle = ["|", "/", "-", "\\"];
window.$mli = setInterval(function () { i = (i + 1) % cycle.length; document.querySelector('.s1').innerHTML = cycle[i]; document.querySelector('.s2').innerHTML = cycle[(i + 2) % cycle.length]; }, 100)
@@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "Meditations",
"name": "Meditations",
"icons": [
{
"src": "favicon.ico",
@@ -12,4 +12,4 @@
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
}
Oops, something went wrong.

0 comments on commit fabbf2d

Please sign in to comment.
You can’t perform that action at this time.