server.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package main
  2. import (
  3. "context"
  4. "embed"
  5. "log"
  6. "net/http"
  7. "os"
  8. "github.com/99designs/gqlgen/graphql/handler"
  9. "github.com/99designs/gqlgen/graphql/handler/extension"
  10. "github.com/99designs/gqlgen/graphql/handler/lru"
  11. "github.com/99designs/gqlgen/graphql/handler/transport"
  12. "github.com/99designs/gqlgen/graphql/playground"
  13. "github.com/vektah/gqlparser/v2"
  14. "github.com/vektah/gqlparser/v2/ast"
  15. "gogs.dmsc.dev/arp/auth"
  16. "gogs.dmsc.dev/arp/graph"
  17. "gogs.dmsc.dev/arp/mcp"
  18. "gogs.dmsc.dev/arp/models"
  19. "gorm.io/driver/sqlite"
  20. "gorm.io/gorm"
  21. )
  22. //go:embed graph/schema.graphqls
  23. var schemaFS embed.FS
  24. const defaultPort = "8080"
  25. func main() {
  26. port := os.Getenv("PORT")
  27. if port == "" {
  28. port = defaultPort
  29. }
  30. // Initialize database
  31. db, err := gorm.Open(sqlite.Open("arp.db"), &gorm.Config{})
  32. if err != nil {
  33. log.Fatal("failed to connect database:", err)
  34. }
  35. // Run auto-migration for all models
  36. err = db.AutoMigrate(&models.User{}, &models.Role{}, &models.Permission{}, &models.Service{}, &models.Task{}, &models.TaskStatus{}, &models.Message{}, &models.Note{})
  37. if err != nil {
  38. log.Fatal("failed to migrate database:", err)
  39. }
  40. // Create resolver with DB instance using NewResolver for pub/sub support
  41. resolver := graph.NewResolver(db)
  42. srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: resolver}))
  43. srv.AddTransport(transport.Options{})
  44. srv.AddTransport(transport.GET{})
  45. srv.AddTransport(transport.POST{})
  46. srv.AddTransport(&transport.Websocket{
  47. // Authenticate WebSocket connections
  48. InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
  49. // Get token from connection params
  50. token, ok := initPayload["Authorization"].(string)
  51. if !ok {
  52. return ctx, nil, nil
  53. }
  54. // Validate token and add user to context
  55. claims, err := auth.ValidateToken(token)
  56. if err != nil {
  57. return ctx, nil, nil
  58. }
  59. // Convert Claims to UserContext
  60. userCtx := &auth.UserContext{
  61. ID: claims.UserID,
  62. Email: claims.Email,
  63. Roles: claims.Roles,
  64. Permissions: claims.Permissions,
  65. }
  66. ctx = auth.WithUser(ctx, userCtx)
  67. return ctx, nil, nil
  68. },
  69. })
  70. srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))
  71. srv.Use(extension.Introspection{})
  72. srv.Use(extension.AutomaticPersistedQuery{
  73. Cache: lru.New[string](100),
  74. })
  75. // Load GraphQL schema for MCP server (from embedded FS)
  76. schemaStr, err := schemaFS.ReadFile("graph/schema.graphqls")
  77. if err != nil {
  78. log.Fatal("failed to read embedded schema file:", err)
  79. }
  80. schema := gqlparser.MustLoadSchema(&ast.Source{
  81. Name: "schema.graphqls",
  82. Input: string(schemaStr),
  83. })
  84. // Create MCP server
  85. mcpServer := mcp.NewServer(resolver, schema)
  86. http.Handle("/", playground.Handler("GraphQL playground", "/query"))
  87. http.Handle("/query", auth.AuthMiddleware(srv))
  88. http.Handle("/mcp", auth.AuthMiddleware(mcpServer))
  89. http.Handle("/message", auth.AuthMiddleware(http.HandlerFunc(mcpServer.HandleMessage)))
  90. log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
  91. log.Printf("MCP server available at http://localhost:%s/mcp", port)
  92. log.Fatal(http.ListenAndServe(":"+port, nil))
  93. }