From 4b88e6ca09eea91658b794bb98a3479dab0d5850 Mon Sep 17 00:00:00 2001
From: zengdiv
Date: Mon, 4 Nov 2024 23:24:22 +0800
Subject: [PATCH 1/6] feat: add mutation/subscription/query
---
graphql/excute/excute.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/graphql/excute/excute.go b/graphql/excute/excute.go
index 3769fe5..ea37e26 100644
--- a/graphql/excute/excute.go
+++ b/graphql/excute/excute.go
@@ -42,11 +42,11 @@ func ExecuteQuery(ctx *context.Context, query string, variables map[string]any)
var funMap map[string]func(qp *parser.QueryParser, field *ast.Field) (interface{}, error)
switch qp.OperationType {
- case "Mutation":
+ case "Mutation", "mutation":
funMap = mutationMap
- case "Subscription":
+ case "Subscription", "subscription":
funMap = subscriptionMap
- case "Query":
+ case "Query", "query":
funMap = queryMap
default:
e := &errors.ParserError{
From 8b965f2e8c4db29628b70dcddd77a0c4daef9bac Mon Sep 17 00:00:00 2001
From: linty
Date: Tue, 5 Nov 2024 01:37:09 +0800
Subject: [PATCH 2/6] fix: fix enumtype
---
example/user/ide-helper.graphql | 9 ++-
example/user/models/interface.go | 10 +--
example/user/models/model.go | 68 ++++++++++++------
example/user/models/response.go | 16 ++---
example/user/repo/repo.go | 97 +++++++++++++++-----------
example/user/resolver/mutation.go | 24 +++----
example/user/resolver/operation_gen.go | 6 +-
example/user/resolver/query.go | 58 +++++++--------
example/user/schema/post.graphqls | 2 +-
graphql/ast/node.go | 4 ++
graphql/excute/validate.go | 25 +++++--
graphql/model/generate/tpl/model.tpl | 16 +++++
graphql/model/generate/tpl/repo.tpl | 10 +--
13 files changed, 215 insertions(+), 130 deletions(-)
diff --git a/example/user/ide-helper.graphql b/example/user/ide-helper.graphql
index 3d66e7f..4c62452 100644
--- a/example/user/ide-helper.graphql
+++ b/example/user/ide-helper.graphql
@@ -6,13 +6,18 @@ type Subscription
directive @skip(if: Boolean!) on FIELD_DEFINITION
directive @include(if: Boolean!) on FIELD_DEFINITION
-# ORM directive
-directive @paginate(scopes: [String!]) on FIELD_DEFINITION
+# enum 枚举
directive @enum(value: Int!) on FIELD_DEFINITION
+
+# query
+directive @paginate(scopes: [String!]) on FIELD_DEFINITION
+
+# model
directive @index(name: String) on FIELD_DEFINITION
directive @tag(name: String!, value: String!) repeatable on FIELD_DEFINITION
directive @defaultString(value: String!) on FIELD_DEFINITION
directive @defaultInt(value: Int!) on FIELD_DEFINITION
+
directive @unique on FIELD_DEFINITION
directive @model(name: String) on OBJECT
directive @softDeleteModel(name: String) on OBJECT
diff --git a/example/user/models/interface.go b/example/user/models/interface.go
index 69dcbaf..cd48e55 100644
--- a/example/user/models/interface.go
+++ b/example/user/models/interface.go
@@ -4,17 +4,17 @@ package models
-type HasName interface {
- IsHasName()
- GetName() string
-}
-
type Userable interface {
IsUserable()
GetUser() User
GetUserId() int64
}
+type HasName interface {
+ IsHasName()
+ GetName() string
+}
+
type Commentable interface {
IsCommentable()
}
diff --git a/example/user/models/model.go b/example/user/models/model.go
index 371959c..1178706 100644
--- a/example/user/models/model.go
+++ b/example/user/models/model.go
@@ -4,62 +4,90 @@ package models
import "github.com/light-speak/lighthouse/graphql/model"
-type User struct {
+type Article struct {
model.Model
- Name string `json:"name" gorm:"index;type:varchar(255)" `
- MyPosts *[]Post `json:"my_posts" gorm:"comment:五二零" `
+ Name string `json:"name" gorm:"type:varchar(255)" `
+ Content string `json:"content" gorm:"type:varchar(255)" `
}
-func (*User) IsModel() bool { return true }
-func (*User) IsHasName() bool { return true }
-func (this *User) GetName() string { return this.Name }
-func (*User) TableName() string { return "users" }
-func (*User) TypeName() string { return "user" }
+func (*Article) IsModel() bool { return true }
+func (*Article) TableName() string { return "articles" }
+func (*Article) TypeName() string { return "article" }
+var ArticleEnumFields = map[string]func(interface{}) interface{} {
+}
type Post struct {
model.ModelSoftDelete
Title string `json:"title" gorm:"index;type:varchar(255)" `
Content string `json:"content" gorm:"type:varchar(255)" `
- TagId int64 `json:"tag_id" `
BackId int64 `json:"back_id" `
- Enum TestEnum `json:"enum" `
- UserId int64 `json:"user_id" gorm:"index" `
IsBool bool `json:"is_bool" gorm:"default:false" `
User *User `json:"user" `
+ UserId int64 `json:"user_id" gorm:"index" `
+ TagId int64 `json:"tag_id" `
+ Enum TestEnum `json:"enum" `
}
func (*Post) IsModel() bool { return true }
func (*Post) TableName() string { return "posts" }
func (*Post) TypeName() string { return "post" }
+var PostEnumFields = map[string]func(interface{}) interface{} {
+ "enum": func(value interface{}) interface{} {
+ switch v := value.(type) {
+ case int64:
+ return TestEnum(v)
+ case int8:
+ return TestEnum(v)
+ default:
+ return v
+ }
+ },
+}
type Comment struct {
model.Model
Content string `json:"content" gorm:"type:varchar(255)" `
CommentableId int64 `json:"commentable_id" gorm:"index:commentable" `
CommentableType CommentableType `gorm:"index:commentable" json:"commentable_type" `
- Commentable interface{} `gorm:"-" json:"commentable" `
+ Commentable interface{} `json:"commentable" gorm:"-" `
}
func (*Comment) IsModel() bool { return true }
func (*Comment) TableName() string { return "comments" }
func (*Comment) TypeName() string { return "comment" }
+var CommentEnumFields = map[string]func(interface{}) interface{} {
+ "commentableType": func(value interface{}) interface{} {
+ switch v := value.(type) {
+ case int64:
+ return CommentableType(v)
+ case int8:
+ return CommentableType(v)
+ default:
+ return v
+ }
+ },
+}
-type Article struct {
+type User struct {
model.Model
- Name string `json:"name" gorm:"type:varchar(255)" `
- Content string `json:"content" gorm:"type:varchar(255)" `
+ Name string `json:"name" gorm:"index;type:varchar(255)" `
+ MyPosts *[]Post `json:"my_posts" gorm:"comment:五二零" `
}
-func (*Article) IsModel() bool { return true }
-func (*Article) TableName() string { return "articles" }
-func (*Article) TypeName() string { return "article" }
+func (*User) IsModel() bool { return true }
+func (*User) IsHasName() bool { return true }
+func (this *User) GetName() string { return this.Name }
+func (*User) TableName() string { return "users" }
+func (*User) TypeName() string { return "user" }
+var UserEnumFields = map[string]func(interface{}) interface{} {
+}
func Migrate() error {
return model.GetDB().AutoMigrate(
- &User{},
+ &Article{},
&Post{},
&Comment{},
- &Article{},
+ &User{},
)
}
\ No newline at end of file
diff --git a/example/user/models/response.go b/example/user/models/response.go
index ffa2547..3791b58 100644
--- a/example/user/models/response.go
+++ b/example/user/models/response.go
@@ -4,22 +4,22 @@ package models
import "github.com/light-speak/lighthouse/graphql/model"
-type UserPaginateResponse struct {
- Data *[]*User `json:"data" `
+type PostPaginateResponse struct {
+ Data *[]*Post `json:"data" `
PaginateInfo *model.PaginateInfo `json:"paginate_info" `
}
type LoginResponse struct {
+ User *User `json:"user" `
Token string `json:"token" gorm:"type:varchar(255)" `
Authorization string `json:"authorization" gorm:"type:varchar(255)" `
- User *User `json:"user" `
}
-type Test struct {
- Test string `json:"test" gorm:"type:varchar(255)" `
+type UserPaginateResponse struct {
+ Data *[]*User `json:"data" `
+ PaginateInfo *model.PaginateInfo `json:"paginate_info" `
}
-type PostPaginateResponse struct {
- Data *[]*Post `json:"data" `
- PaginateInfo *model.PaginateInfo `json:"paginate_info" `
+type Test struct {
+ Test string `json:"test" gorm:"type:varchar(255)" `
}
diff --git a/example/user/repo/repo.go b/example/user/repo/repo.go
index 493604f..4aa3a34 100644
--- a/example/user/repo/repo.go
+++ b/example/user/repo/repo.go
@@ -2,51 +2,53 @@
package repo
import (
- "github.com/light-speak/lighthouse/graphql/ast"
"github.com/light-speak/lighthouse/context"
- "gorm.io/gorm"
- "user/models"
"github.com/light-speak/lighthouse/graphql/model"
+ "user/models"
+ "gorm.io/gorm"
)
-func Provide__User() map[string]*ast.Relation { return map[string]*ast.Relation{"created_at": {},"id": {},"myPosts": {Name: "post", RelationType: ast.RelationTypeHasMany, ForeignKey: "user_id", Reference: "id"},"name": {},"updated_at": {},}}
-func Load__User(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
- return model.GetLoader[int64](model.GetDB(), "users", field).Load(key)
+func Load__Article(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
+ return model.GetLoader[int64](model.GetDB(), "articles", field).Load(key)
}
-func LoadList__User(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) {
- return model.GetLoader[int64](model.GetDB(), "users", field).LoadList(key)
+func LoadList__Article(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) {
+ return model.GetLoader[int64](model.GetDB(), "articles", field).LoadList(key)
}
-func Query__User(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB {
- return model.GetDB().Model(&models.User{}).Scopes(scopes...)
+func Query__Article(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB {
+ return model.GetDB().Model(&models.Article{}).Scopes(scopes...)
}
-func First__User(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) {
+func First__Article(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) {
var err error
if data == nil {
data = make(map[string]interface{})
- err = Query__User().Scopes(scopes...).First(data).Error
+ err = Query__Article().Scopes(scopes...).First(data).Error
if err != nil {
return nil, err
}
}
+ for key, value := range data {
+ if fn, ok := models.ArticleEnumFields[key]; ok {
+ data[key] = fn(value)
+ }
+ }
return data, nil
}
-func List__User(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
+func List__Article(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
var err error
if datas == nil {
datas = make([]map[string]interface{}, 0)
- err = Query__User().Scopes(scopes...).Find(&datas).Error
+ err = Query__Article().Scopes(scopes...).Find(&datas).Error
if err != nil {
return nil, err
}
}
return datas, nil
}
-func Count__User(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
+func Count__Article(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
var count int64
- err := Query__User().Scopes(scopes...).Count(&count).Error
+ err := Query__Article().Scopes(scopes...).Count(&count).Error
return count, err
}
-func Provide__Post() map[string]*ast.Relation { return map[string]*ast.Relation{"BackId": {},"IsBool": {},"content": {},"created_at": {},"deleted_at": {},"enum": {},"id": {},"tagId": {},"title": {},"updated_at": {},"user": {Name: "user", RelationType: ast.RelationTypeBelongsTo, ForeignKey: "user_id", Reference: "id"},"userId": {},}}
func Load__Post(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
return model.GetLoader[int64](model.GetDB(), "posts", field).Load(key)
}
@@ -65,6 +67,11 @@ func First__Post(ctx *context.Context, data map[string]interface{}, scopes ...fu
return nil, err
}
}
+ for key, value := range data {
+ if fn, ok := models.PostEnumFields[key]; ok {
+ data[key] = fn(value)
+ }
+ }
return data, nil
}
func List__Post(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
@@ -83,7 +90,6 @@ func Count__Post(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
err := Query__Post().Scopes(scopes...).Count(&count).Error
return count, err
}
-func Provide__Comment() map[string]*ast.Relation { return map[string]*ast.Relation{"commentable": {Name: "", RelationType: ast.RelationTypeMorphTo, ForeignKey: "", Reference: "id"},"commentableId": {},"commentableType": {},"content": {},"created_at": {},"id": {},"updated_at": {},}}
func Load__Comment(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
return model.GetLoader[int64](model.GetDB(), "comments", field).Load(key)
}
@@ -102,6 +108,11 @@ func First__Comment(ctx *context.Context, data map[string]interface{}, scopes ..
return nil, err
}
}
+ for key, value := range data {
+ if fn, ok := models.CommentEnumFields[key]; ok {
+ data[key] = fn(value)
+ }
+ }
return data, nil
}
func List__Comment(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
@@ -120,51 +131,55 @@ func Count__Comment(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
err := Query__Comment().Scopes(scopes...).Count(&count).Error
return count, err
}
-func Provide__Article() map[string]*ast.Relation { return map[string]*ast.Relation{"content": {},"created_at": {},"id": {},"name": {},"updated_at": {},}}
-func Load__Article(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
- return model.GetLoader[int64](model.GetDB(), "articles", field).Load(key)
+func Load__User(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
+ return model.GetLoader[int64](model.GetDB(), "users", field).Load(key)
}
-func LoadList__Article(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) {
- return model.GetLoader[int64](model.GetDB(), "articles", field).LoadList(key)
+func LoadList__User(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) {
+ return model.GetLoader[int64](model.GetDB(), "users", field).LoadList(key)
}
-func Query__Article(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB {
- return model.GetDB().Model(&models.Article{}).Scopes(scopes...)
+func Query__User(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB {
+ return model.GetDB().Model(&models.User{}).Scopes(scopes...)
}
-func First__Article(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) {
+func First__User(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) {
var err error
if data == nil {
data = make(map[string]interface{})
- err = Query__Article().Scopes(scopes...).First(data).Error
+ err = Query__User().Scopes(scopes...).First(data).Error
if err != nil {
return nil, err
}
}
+ for key, value := range data {
+ if fn, ok := models.UserEnumFields[key]; ok {
+ data[key] = fn(value)
+ }
+ }
return data, nil
}
-func List__Article(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
+func List__User(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
var err error
if datas == nil {
datas = make([]map[string]interface{}, 0)
- err = Query__Article().Scopes(scopes...).Find(&datas).Error
+ err = Query__User().Scopes(scopes...).Find(&datas).Error
if err != nil {
return nil, err
}
}
return datas, nil
}
-func Count__Article(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
+func Count__User(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) {
var count int64
- err := Query__Article().Scopes(scopes...).Count(&count).Error
+ err := Query__User().Scopes(scopes...).Count(&count).Error
return count, err
}
func init() {
- model.AddQuickFirst("User", First__User)
- model.AddQuickList("User", List__User)
- model.AddQuickLoad("User", Load__User)
- model.AddQuickLoadList("User", LoadList__User)
- model.AddQuickCount("User", Count__User)
+ model.AddQuickFirst("Article", First__Article)
+ model.AddQuickList("Article", List__Article)
+ model.AddQuickLoad("Article", Load__Article)
+ model.AddQuickLoadList("Article", LoadList__Article)
+ model.AddQuickCount("Article", Count__Article)
model.AddQuickFirst("Post", First__Post)
model.AddQuickList("Post", List__Post)
model.AddQuickLoad("Post", Load__Post)
@@ -175,9 +190,9 @@ func init() {
model.AddQuickLoad("Comment", Load__Comment)
model.AddQuickLoadList("Comment", LoadList__Comment)
model.AddQuickCount("Comment", Count__Comment)
- model.AddQuickFirst("Article", First__Article)
- model.AddQuickList("Article", List__Article)
- model.AddQuickLoad("Article", Load__Article)
- model.AddQuickLoadList("Article", LoadList__Article)
- model.AddQuickCount("Article", Count__Article)
+ model.AddQuickFirst("User", First__User)
+ model.AddQuickList("User", List__User)
+ model.AddQuickLoad("User", Load__User)
+ model.AddQuickLoadList("User", LoadList__User)
+ model.AddQuickCount("User", Count__User)
}
diff --git a/example/user/resolver/mutation.go b/example/user/resolver/mutation.go
index 8aebd28..6214099 100644
--- a/example/user/resolver/mutation.go
+++ b/example/user/resolver/mutation.go
@@ -2,16 +2,16 @@
package resolver
import (
- "fmt"
- "user/models"
-
- "github.com/light-speak/lighthouse/auth"
- "github.com/light-speak/lighthouse/context"
- "github.com/light-speak/lighthouse/graphql/model"
- "github.com/light-speak/lighthouse/log"
+ "github.com/light-speak/lighthouse/graphql/model"
+ "fmt"
+ "user/models"
+ "github.com/light-speak/lighthouse/auth"
+ "github.com/light-speak/lighthouse/log"
+ "github.com/light-speak/lighthouse/context"
)
-func (r *Resolver) LoginResolver(ctx *context.Context, name string) (*models.LoginResponse, error) {
+
+func (r *Resolver) LoginResolver(ctx *context.Context,name string) (*models.LoginResponse, error) {
// Func:Login user code start. Do not remove this comment.
user := &models.User{}
db := model.GetDB()
@@ -28,10 +28,10 @@ func (r *Resolver) LoginResolver(ctx *context.Context, name string) (*models.Log
Token: token,
Authorization: fmt.Sprintf("Bearer %s", token),
}, nil
- // Func:Login user code end. Do not remove this comment.
+ // Func:Login user code end. Do not remove this comment.
}
-func (r *Resolver) CreatePostResolver(ctx *context.Context, input *models.TestInput) (*models.Post, error) {
+func (r *Resolver) CreatePostResolver(ctx *context.Context,input *models.TestInput) (*models.Post, error) {
// Func:CreatePost user code start. Do not remove this comment.
panic("not implement")
- // Func:CreatePost user code end. Do not remove this comment.
-}
+ // Func:CreatePost user code end. Do not remove this comment.
+}
\ No newline at end of file
diff --git a/example/user/resolver/operation_gen.go b/example/user/resolver/operation_gen.go
index bcee27d..a185d27 100644
--- a/example/user/resolver/operation_gen.go
+++ b/example/user/resolver/operation_gen.go
@@ -2,13 +2,13 @@
package resolver
import (
+ "github.com/light-speak/lighthouse/graphql"
"github.com/light-speak/lighthouse/context"
- "user/models"
"github.com/light-speak/lighthouse/graphql/model"
+ "github.com/light-speak/lighthouse/graphql/excute"
"fmt"
- "github.com/light-speak/lighthouse/graphql"
"github.com/light-speak/lighthouse/resolve"
- "github.com/light-speak/lighthouse/graphql/excute"
+ "user/models"
)
func init() {
diff --git a/example/user/resolver/query.go b/example/user/resolver/query.go
index 553b5e0..8b592e7 100644
--- a/example/user/resolver/query.go
+++ b/example/user/resolver/query.go
@@ -2,19 +2,31 @@
package resolver
import (
- "fmt"
- "github.com/light-speak/lighthouse/context"
"user/models"
"github.com/light-speak/lighthouse/log"
+ "github.com/light-speak/lighthouse/context"
"github.com/light-speak/lighthouse/graphql/model"
+ "fmt"
)
-func (r *Resolver) TestPostIdResolver(ctx *context.Context,id int64) (*models.Post, error) {
- // Func:TestPostId user code start. Do not remove this comment.
- log.Debug().Msgf("id: %d", id)
- return nil, nil
- // Func:TestPostId user code end. Do not remove this comment.
+func (r *Resolver) TestPostInputResolver(ctx *context.Context,input *models.TestInput) (string, error) {
+ // Func:TestPostInput user code start. Do not remove this comment.
+ res := fmt.Sprintf("input: %+v", input)
+ return res, nil
+ // Func:TestPostInput user code end. Do not remove this comment.
+}
+func (r *Resolver) TestNullableEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) {
+ // Func:TestNullableEnum user code start. Do not remove this comment.
+ panic("not implement")
+ // Func:TestNullableEnum user code end. Do not remove this comment.
+}
+func (r *Resolver) TestPostEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) {
+ // Func:TestPostEnum user code start. Do not remove this comment.
+ log.Debug().Msgf("enum: %+v", enum)
+ res := fmt.Sprintf("啥也不是!:%v", *enum == models.TestEnumA)
+ return res, nil
+ // Func:TestPostEnum user code end. Do not remove this comment.
}
func (r *Resolver) GetPostResolver(ctx *context.Context,fuck string) (*models.Post, error) {
// Func:GetPost user code start. Do not remove this comment.
@@ -25,34 +37,17 @@ func (r *Resolver) GetPostResolver(ctx *context.Context,fuck string) (*models.Po
return post, nil
// Func:GetPost user code end. Do not remove this comment.
}
-func (r *Resolver) TestNullableEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) {
- // Func:TestNullableEnum user code start. Do not remove this comment.
- panic("not implement")
- // Func:TestNullableEnum user code end. Do not remove this comment.
-}
-func (r *Resolver) GetPostIdsResolver(ctx *context.Context) ([]int64, error) {
- // Func:GetPostIds user code start. Do not remove this comment.
- return []int64{1, 2, 3}, nil
- // Func:GetPostIds user code end. Do not remove this comment.
+func (r *Resolver) TestPostIdResolver(ctx *context.Context,id int64) (*models.Post, error) {
+ // Func:TestPostId user code start. Do not remove this comment.
+ log.Debug().Msgf("id: %d", id)
+ return nil, nil
+ // Func:TestPostId user code end. Do not remove this comment.
}
func (r *Resolver) TestPostIntResolver(ctx *context.Context,id bool) (*models.Post, error) {
// Func:TestPostInt user code start. Do not remove this comment.
return nil, nil
// Func:TestPostInt user code end. Do not remove this comment.
}
-func (r *Resolver) TestPostEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) {
- // Func:TestPostEnum user code start. Do not remove this comment.
- log.Debug().Msgf("enum: %+v", enum)
- res := fmt.Sprintf("啥也不是!:%v", *enum == models.TestEnumA)
- return res, nil
- // Func:TestPostEnum user code end. Do not remove this comment.
-}
-func (r *Resolver) TestPostInputResolver(ctx *context.Context,input *models.TestInput) (string, error) {
- // Func:TestPostInput user code start. Do not remove this comment.
- res := fmt.Sprintf("input: %+v", input)
- return res, nil
- // Func:TestPostInput user code end. Do not remove this comment.
-}
func (r *Resolver) GetPostsResolver(ctx *context.Context,fuck string) ([]*models.Post, error) {
// Func:GetPosts user code start. Do not remove this comment.
posts := []*models.Post{}
@@ -60,4 +55,9 @@ func (r *Resolver) GetPostsResolver(ctx *context.Context,fuck string) ([]*models
db.Find(&posts)
return posts, nil
// Func:GetPosts user code end. Do not remove this comment.
+}
+func (r *Resolver) GetPostIdsResolver(ctx *context.Context) ([]int64, error) {
+ // Func:GetPostIds user code start. Do not remove this comment.
+ return []int64{1, 2, 3}, nil
+ // Func:GetPostIds user code end. Do not remove this comment.
}
\ No newline at end of file
diff --git a/example/user/schema/post.graphqls b/example/user/schema/post.graphqls
index a677dda..4d3091e 100644
--- a/example/user/schema/post.graphqls
+++ b/example/user/schema/post.graphqls
@@ -20,7 +20,7 @@ extend type Query {
testPostInt(id: Boolean!): Post
testPostEnum(enum: TestEnum!): String!
testPostInput(input: TestInput!): String!
- testNullableEnum(enum: TestEnum): String!
+ testNullableEnum(enum: TestEnum): String!
}
extend type Mutation {
diff --git a/graphql/ast/node.go b/graphql/ast/node.go
index 92a4578..8850a6a 100644
--- a/graphql/ast/node.go
+++ b/graphql/ast/node.go
@@ -532,6 +532,10 @@ func (t *TypeRef) IsObject() bool {
return t.Kind == KindObject
}
+func (t *TypeRef) IsEnum() bool {
+ return t.GetRealType().Kind == KindEnum
+}
+
func (t *TypeRef) GetRealType() *TypeRef {
if t.Kind == KindNonNull {
return t.OfType.GetRealType()
diff --git a/graphql/excute/validate.go b/graphql/excute/validate.go
index fa8ae63..b3a71b6 100644
--- a/graphql/excute/validate.go
+++ b/graphql/excute/validate.go
@@ -1,6 +1,8 @@
package excute
import (
+ "fmt"
+
"github.com/light-speak/lighthouse/errors"
"github.com/light-speak/lighthouse/graphql/ast"
"github.com/light-speak/lighthouse/graphql/model"
@@ -13,15 +15,30 @@ func ValidateValue(field *ast.Field, value interface{}, isVariable bool) (interf
switch realType.Kind {
case ast.KindScalar:
- v, err = realType.TypeNode.(*ast.ScalarNode).ScalarType.Serialize(value, field.GetLocation())
- if err != nil {
+ if scalarNode, ok := realType.TypeNode.(*ast.ScalarNode); ok {
+ v, err = scalarNode.ScalarType.Serialize(value, field.GetLocation())
+ if err != nil {
+ return nil, &errors.GraphQLError{
+ Message: err.Error(),
+ Locations: []*errors.GraphqlLocation{field.GetLocation()},
+ }
+ }
+ } else {
return nil, &errors.GraphQLError{
- Message: err.Error(),
+ Message: fmt.Sprintf("scalar type is not a scalar node, field: %s", field.Name),
Locations: []*errors.GraphqlLocation{field.GetLocation()},
}
}
case ast.KindEnum:
- v = value.(model.EnumInterface).ToString()
+
+ if e, ok := value.(model.EnumInterface); ok {
+ v = e.ToString()
+ } else {
+ return nil, &errors.GraphQLError{
+ Message: fmt.Sprintf("enum value type not supported, field: %s, got %v , type: %T", field.Name, value, value),
+ Locations: []*errors.GraphqlLocation{field.GetLocation()},
+ }
+ }
}
return v, nil
}
diff --git a/graphql/model/generate/tpl/model.tpl b/graphql/model/generate/tpl/model.tpl
index e9b20f7..b93a8cc 100644
--- a/graphql/model/generate/tpl/model.tpl
+++ b/graphql/model/generate/tpl/model.tpl
@@ -9,6 +9,22 @@ func (this *{{ $name | ucFirst }}) Get{{ .Name | ucFirst }}() {{ false | .Type.G
{{- end }}
func (*{{ $name | ucFirst }}) TableName() string { return "{{ if ne .Table "" }}{{ .Table }}{{ else }}{{ .Name | pluralize | lcFirst }}{{ end }}" }
func (*{{ $name | ucFirst }}) TypeName() string { return "{{ .Name | lcFirst }}" }
+var {{ $name | ucFirst }}EnumFields = map[string]func(interface{}) interface{} {
+ {{- range .Fields }}
+ {{- if .Type.IsEnum }}
+ "{{ .Name | lcFirst }}": func(value interface{}) interface{} {
+ switch v := value.(type) {
+ case int64:
+ return {{ .Type.GetRealType.Name }}(v)
+ case int8:
+ return {{ .Type.GetRealType.Name }}(v)
+ default:
+ return v
+ }
+ },
+ {{- end }}
+ {{- end }}
+}
{{ end }}
func Migrate() error {
diff --git a/graphql/model/generate/tpl/repo.tpl b/graphql/model/generate/tpl/repo.tpl
index 3170fec..2606956 100644
--- a/graphql/model/generate/tpl/repo.tpl
+++ b/graphql/model/generate/tpl/repo.tpl
@@ -1,10 +1,5 @@
{{ range .Nodes }}
{{- $name := .Name -}}
-func Provide__{{ $name | ucFirst }}() map[string]*ast.Relation { return map[string]*ast.Relation{
- {{- range .Fields }}
- {{- if ne .Name "__typename" }}"{{ .Name }}": {{ if .Relation }}{{ buildRelation . }}{{ else }}{}{{ end }},{{ end -}}
- {{- end -}}
-}}
func Load__{{ $name | ucFirst }}(ctx *context.Context, key int64, field string) (map[string]interface{}, error) {
return model.GetLoader[int64](model.GetDB(), "{{ if ne .Table "" }}{{ .Table }}{{ else }}{{ $name | pluralize | lcFirst }}{{ end }}", field).Load(key)
}
@@ -23,6 +18,11 @@ func First__{{ $name | ucFirst }}(ctx *context.Context, data map[string]interfac
return nil, err
}
}
+ for key, value := range data {
+ if fn, ok := models.{{ $name | ucFirst }}EnumFields[key]; ok {
+ data[key] = fn(value)
+ }
+ }
return data, nil
}
func List__{{ $name | ucFirst }}(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) {
From 20849a4edb581d6e92dafbc51960eea271878752 Mon Sep 17 00:00:00 2001
From: linty
Date: Tue, 5 Nov 2024 02:01:12 +0800
Subject: [PATCH 3/6] feat: Sorted out the annotations, idehelper, and added
several relation annotations.
---
.../initialize/one/tpl/ide-helper.tpl | 42 +++++++++++++--
example/user/ide-helper.graphql | 34 +++++++++---
graphql/ast/directive.go | 5 ++
graphql/ast/directive/belongsto.go | 4 +-
graphql/ast/directive/hasmany.go | 12 ++---
graphql/ast/directive/morphto.go | 8 +--
graphql/parser/directives.go | 52 +++++++++++++++++--
7 files changed, 129 insertions(+), 28 deletions(-)
diff --git a/command/cli/generate/initialize/one/tpl/ide-helper.tpl b/command/cli/generate/initialize/one/tpl/ide-helper.tpl
index c7e06a4..cfc1d35 100644
--- a/command/cli/generate/initialize/one/tpl/ide-helper.tpl
+++ b/command/cli/generate/initialize/one/tpl/ide-helper.tpl
@@ -2,8 +2,44 @@ type Query
type Mutation
type Subscription
-
-directive @paginate(scopes: [String!]) on FIELD_DEFINITION
directive @skip(if: Boolean!) on FIELD_DEFINITION
directive @include(if: Boolean!) on FIELD_DEFINITION
-directive @enum(value: Int!) on FIELD_DEFINITION
\ No newline at end of file
+directive @deprecated(reason: String) on FIELD_DEFINITION
+
+# enum 枚举
+directive @enum(value: Int!) on FIELD_DEFINITION
+
+
+# model
+directive @index(name: String) on FIELD_DEFINITION
+directive @tag(name: String!, value: String!) on FIELD_DEFINITION
+directive @defaultString(value: String!) on FIELD_DEFINITION
+directive @defaultInt(value: Int!) on FIELD_DEFINITION
+directive @unique on FIELD_DEFINITION
+directive @model(name: String) on OBJECT
+directive @softDeleteModel(name: String) on OBJECT
+
+
+# query returnType
+directive @paginate(scopes: [String!]) on FIELD_DEFINITION
+directive @find(scopes: [String!]) on FIELD_DEFINITION
+directive @first(scopes: [String!]) on FIELD_DEFINITION
+
+
+# argument filter
+directive @in(field: String) on ARGUMENT_DEFINITION
+directive @eq(field: String) on ARGUMENT_DEFINITION
+directive @neq(field: String) on ARGUMENT_DEFINITION
+directive @gt(field: String) on ARGUMENT_DEFINITION
+directive @gte(field: String) on ARGUMENT_DEFINITION
+directive @lt(field: String) on ARGUMENT_DEFINITION
+directive @lte(field: String) on ARGUMENT_DEFINITION
+directive @like(field: String) on ARGUMENT_DEFINITION
+directive @notIn(field: String) on ARGUMENT_DEFINITION
+
+# relation
+directive @belongsTo(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @hasMany(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @hasOne(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @morphTo(morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
+directive @morphToMany(relation: String!, morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
diff --git a/example/user/ide-helper.graphql b/example/user/ide-helper.graphql
index 4c62452..ff0e587 100644
--- a/example/user/ide-helper.graphql
+++ b/example/user/ide-helper.graphql
@@ -5,22 +5,42 @@ type Subscription
directive @skip(if: Boolean!) on FIELD_DEFINITION
directive @include(if: Boolean!) on FIELD_DEFINITION
+directive @deprecated(reason: String) on FIELD_DEFINITION
# enum 枚举
directive @enum(value: Int!) on FIELD_DEFINITION
-# query
-directive @paginate(scopes: [String!]) on FIELD_DEFINITION
# model
directive @index(name: String) on FIELD_DEFINITION
-directive @tag(name: String!, value: String!) repeatable on FIELD_DEFINITION
+directive @tag(name: String!, value: String!) on FIELD_DEFINITION
directive @defaultString(value: String!) on FIELD_DEFINITION
directive @defaultInt(value: Int!) on FIELD_DEFINITION
-
directive @unique on FIELD_DEFINITION
directive @model(name: String) on OBJECT
directive @softDeleteModel(name: String) on OBJECT
-directive @deprecated(reason: String) on FIELD_DEFINITION
-directive @in(fields: [String!]!) on ARGUMENT_DEFINITION
-directive @find(scopes: [String!]) on FIELD_DEFINITION
\ No newline at end of file
+
+
+# query returnType
+directive @paginate(scopes: [String!]) on FIELD_DEFINITION
+directive @find(scopes: [String!]) on FIELD_DEFINITION
+directive @first(scopes: [String!]) on FIELD_DEFINITION
+
+
+# argument filter
+directive @in(field: String) on ARGUMENT_DEFINITION
+directive @eq(field: String) on ARGUMENT_DEFINITION
+directive @neq(field: String) on ARGUMENT_DEFINITION
+directive @gt(field: String) on ARGUMENT_DEFINITION
+directive @gte(field: String) on ARGUMENT_DEFINITION
+directive @lt(field: String) on ARGUMENT_DEFINITION
+directive @lte(field: String) on ARGUMENT_DEFINITION
+directive @like(field: String) on ARGUMENT_DEFINITION
+directive @notIn(field: String) on ARGUMENT_DEFINITION
+
+# relation
+directive @belongsTo(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @hasMany(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @hasOne(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
+directive @morphTo(morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
+directive @morphToMany(relation: String!, morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
diff --git a/graphql/ast/directive.go b/graphql/ast/directive.go
index 048d019..ec38516 100644
--- a/graphql/ast/directive.go
+++ b/graphql/ast/directive.go
@@ -4,6 +4,7 @@ import "github.com/light-speak/lighthouse/errors"
var fieldDirectiveMap = make(map[string]func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface)
var objectDirectiveMap = make(map[string]func(o *ObjectNode, d *Directive, store *NodeStore) errors.GraphqlErrorInterface)
+var fieldRuntimeDirectiveMap = make(map[string]func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface)
func AddFieldDirective(name string, fn func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) {
fieldDirectiveMap[name] = fn
@@ -13,6 +14,10 @@ func AddObjectDirective(name string, fn func(o *ObjectNode, d *Directive, store
objectDirectiveMap[name] = fn
}
+func AddFieldRuntimeDirective(name string, fn func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) {
+ fieldRuntimeDirectiveMap[name] = fn
+}
+
func (f *Field) ParseFieldDirectives(store *NodeStore, parent Node) errors.GraphqlErrorInterface {
for _, directive := range f.Directives {
if fn, ok := fieldDirectiveMap[directive.Name]; ok {
diff --git a/graphql/ast/directive/belongsto.go b/graphql/ast/directive/belongsto.go
index 464fc68..568bbab 100644
--- a/graphql/ast/directive/belongsto.go
+++ b/graphql/ast/directive/belongsto.go
@@ -11,12 +11,12 @@ func handlerBelongsTo(f *ast.Field, d *ast.Directive, store *ast.NodeStore, pare
RelationType: ast.RelationTypeBelongsTo,
}
if relationName := d.GetArg("relation"); relationName != nil {
- relation.Name = relationName.Value.(string)
+ relation.Name = utils.SnakeCase(relationName.Value.(string))
} else {
relation.Name = utils.LcFirst(f.Name)
}
if reference := d.GetArg("reference"); reference != nil {
- relation.Reference = reference.Value.(string)
+ relation.Reference = utils.SnakeCase(reference.Value.(string))
} else {
relation.Reference = "id"
}
diff --git a/graphql/ast/directive/hasmany.go b/graphql/ast/directive/hasmany.go
index 1d042d0..7575081 100644
--- a/graphql/ast/directive/hasmany.go
+++ b/graphql/ast/directive/hasmany.go
@@ -12,20 +12,14 @@ func handlerHasMany(f *ast.Field, d *ast.Directive, store *ast.NodeStore, parent
RelationType: ast.RelationTypeHasMany,
}
if relationName := d.GetArg("relation"); relationName != nil {
- relation.Name = relationName.Value.(string)
+ relation.Name = utils.SnakeCase(relationName.Value.(string))
} else {
- return &errors.GraphQLError{
- Message: "relation name is required for hasMany directive",
- Locations: []*errors.GraphqlLocation{d.GetLocation()},
- }
+ relation.Name = utils.SnakeCase(f.Type.GetRealType().Name)
}
if foreignKey := d.GetArg("foreignKey"); foreignKey != nil {
relation.ForeignKey = utils.SnakeCase(foreignKey.Value.(string))
} else {
- return &errors.GraphQLError{
- Message: "foreign key is required for hasMany directive",
- Locations: []*errors.GraphqlLocation{d.GetLocation()},
- }
+ relation.ForeignKey = utils.SnakeCase(relation.Name) + "_id"
}
if reference := d.GetArg("reference"); reference != nil {
relation.Reference = utils.SnakeCase(reference.Value.(string))
diff --git a/graphql/ast/directive/morphto.go b/graphql/ast/directive/morphto.go
index 0e18b37..a4b9bab 100644
--- a/graphql/ast/directive/morphto.go
+++ b/graphql/ast/directive/morphto.go
@@ -13,17 +13,17 @@ func handlerMorphTo(f *ast.Field, d *ast.Directive, store *ast.NodeStore, parent
RelationType: ast.RelationTypeMorphTo,
}
if morphType := d.GetArg("morphType"); morphType != nil {
- relation.MorphType = morphType.Value.(string)
+ relation.MorphType = utils.SnakeCase(morphType.Value.(string))
} else {
relation.MorphType = fmt.Sprintf("%s_type", utils.LcFirst(f.Name))
}
if morphKey := d.GetArg("morphKey"); morphKey != nil {
- relation.MorphKey = morphKey.Value.(string)
+ relation.MorphKey = utils.SnakeCase(morphKey.Value.(string))
} else {
- relation.MorphKey = fmt.Sprintf("%s_id", utils.LcFirst(f.Name))
+ relation.MorphKey = fmt.Sprintf("%s_id", utils.SnakeCase(f.Name))
}
if reference := d.GetArg("reference"); reference != nil {
- relation.Reference = reference.Value.(string)
+ relation.Reference = utils.SnakeCase(reference.Value.(string))
} else {
relation.Reference = "id"
}
diff --git a/graphql/parser/directives.go b/graphql/parser/directives.go
index 5ac99db..19d7422 100644
--- a/graphql/parser/directives.go
+++ b/graphql/parser/directives.go
@@ -303,11 +303,11 @@ func (p *Parser) addRelationDirective() {
Args: map[string]*ast.Argument{
"relation": {
Name: "relation",
- Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}},
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
},
"foreignKey": {
Name: "foreignKey",
- Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}},
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
},
"reference": {
Name: "reference",
@@ -322,7 +322,7 @@ func (p *Parser) addRelationDirective() {
Args: map[string]*ast.Argument{
"relation": {
Name: "relation",
- Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}},
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
},
"foreignKey": {
Name: "foreignKey",
@@ -353,6 +353,52 @@ func (p *Parser) addRelationDirective() {
},
},
})
+ // morphToMany
+ p.AddDirectiveDefinition(&ast.DirectiveDefinition{
+ Name: "morphToMany", Description: utils.StrPtr("The field is a relationship with another model."),
+ Locations: []ast.Location{ast.LocationFieldDefinition},
+ Args: map[string]*ast.Argument{
+ "relation": {
+ Name: "relation",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ "morphType": {
+ Name: "morphType",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ "morphKey": {
+ Name: "morphKey",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ "reference": {
+ Name: "reference",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ },
+ })
+ // manyToMany
+ p.AddDirectiveDefinition(&ast.DirectiveDefinition{
+ Name: "manyToMany", Description: utils.StrPtr("The field is a relationship with another model."),
+ Locations: []ast.Location{ast.LocationFieldDefinition},
+ Args: map[string]*ast.Argument{
+ "relation": {
+ Name: "relation",
+ Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}},
+ },
+ "pivot": {
+ Name: "pivot",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ "foreignKey": {
+ Name: "foreignKey",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ "reference": {
+ Name: "reference",
+ Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"},
+ },
+ },
+ })
}
func (p *Parser) addObjectDirective() {
From af6548cd1237da07c7594bb6a2cfae8fc1e1ceda Mon Sep 17 00:00:00 2001
From: linty
Date: Tue, 5 Nov 2024 02:05:40 +0800
Subject: [PATCH 4/6] feat: Support direct line breaks for arguments
---
graphql/parser/parsing.go | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/graphql/parser/parsing.go b/graphql/parser/parsing.go
index 52ee857..1992a5a 100644
--- a/graphql/parser/parsing.go
+++ b/graphql/parser/parsing.go
@@ -255,10 +255,13 @@ func (p *Parser) parseArguments() map[string]*ast.Argument {
}
p.expect(lexer.LeftParent)
for p.currToken.Type != lexer.RightParent {
+ if p.currToken.Type == lexer.Comma {
+ p.expect(lexer.Comma)
+ }
arg := p.parseArgument()
args[arg.Name] = arg
- if p.currToken.Type != lexer.RightParent {
- p.expect(lexer.Comma)
+ if p.currToken.Type == lexer.RightParent {
+ break
}
}
From 23794ceef638bb6ce48df60f3fd6ffdc7fd0e494 Mon Sep 17 00:00:00 2001
From: linty
Date: Tue, 5 Nov 2024 02:07:03 +0800
Subject: [PATCH 5/6] docs: change version => v0.0.4
---
version/version.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/version/version.go b/version/version.go
index 566d4f7..149642f 100644
--- a/version/version.go
+++ b/version/version.go
@@ -1,3 +1,3 @@
package version
-var Version = "v0.0.3"
+var Version = "v0.0.4"
From 92f27dd5089a709a63baeaaa6429d2ff0873914c Mon Sep 17 00:00:00 2001
From: linty
Date: Tue, 5 Nov 2024 02:48:44 +0800
Subject: [PATCH 6/6] docs: readme
---
README.md | 152 +++++++++++++++++++++++++++++--
README_zh.md | 156 ++++++++++++++++++++++++++++++++
example/user/ide-helper.graphql | 1 +
3 files changed, 303 insertions(+), 6 deletions(-)
create mode 100644 README_zh.md
diff --git a/README.md b/README.md
index 755be79..0b64622 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,158 @@
-# Lighthouse
+# **🚢 Lighthouse GraphQL Framework**
+
+[English](https://github.com/light-speak/lighthouse/blob/main/README.md) | [中文](https://github.com/light-speak/lighthouse/blob/main/README_zh.md)
[![CI](https://github.com/light-speak/lighthouse/actions/workflows/main.yml/badge.svg)](https://github.com/light-speak/lighthouse/actions/workflows/main.yml)
[![codecov](https://codecov.io/gh/light-speak/lighthouse/branch/main/graph/badge.svg)](https://codecov.io/gh/light-speak/lighthouse)
[![Go Report Card](https://goreportcard.com/badge/github.com/light-speak/lighthouse)](https://goreportcard.com/report/github.com/light-speak/lighthouse)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+`Lighthouse` is a feature-rich self-developed GraphQL framework designed to simplify GraphQL service development based on microservice architecture. The framework integrates a logging system (using `zeroLog`), supports Elasticsearch, file logging mode and Redis caching, while featuring a flexible directive system and powerful custom configuration capabilities. The framework currently has built-in support for `gorm` and will expand to more ORM options in the future.
+
+## Features
+
+- **Microservice Architecture Support**: Adopts independent microservice mode, doesn't support GraphQL Federation but manages microservices through a custom service registry.
+- **Custom Directives**: Supports rich custom directives for dynamic queries, filtering, relationships, and more.
+- **Extensibility**: Supports various GraphQL file structures through configuration files to flexibly meet different project needs.
+- **ORM Integration**: Currently supports `gorm`, with plans to add support for more ORM libraries.
+- **Logging and Cache Integration**: Integrates `zeroLog` logging system, supports Elasticsearch, file logging, and Redis caching.
+
+## Quick Start
+
+### Installation
+
+1. **Install using `go install`**
+
+ ```bash
+ go install github.com/light-speak/lighthouse@latest
+ ```
+
+2. **Create a new project**
+
+ ```bash
+ lighthouse generate:init
+ ```
+
+### Configuration File (lighthouse.yml)
+
+`lighthouse.yml` is the core configuration file for `lighthouse`, used to specify GraphQL Schema paths, file extensions, ORM settings, etc. Here's an example configuration:
+
+```yaml
+# lighthouse.yml
+
+schema:
+ ext:
+ - graphql # Supported file extensions
+ - graphqls
+ path:
+ - schema # Path to GraphQL Schema files
+ model:
+ orm: gorm # ORM configuration, currently supports gorm
+```
+
+- `schema.ext`: Specifies Schema file extensions, can be `.graphql` or `.graphqls`.
+- `schema.path`: Defines the path to Schema files, framework will automatically load all files in this path.
+- `model.orm`: Currently supports `gorm` as the ORM library.
+
+### Directory Structure
+
+The `example` project structure is as follows:
+
+```plaintext
+.
+├── cmd # CLI related code
+│ ├── cmd.go # Main command entry
+│ ├── migrate
+│ │ └── migrate.go # Database migration logic
+│ └── start
+│ └── start.go # Service start entry
+├── models # Data model definitions
+│ ├── enum.go # Enum type definitions
+│ ├── input.go # Input type definitions
+│ ├── interface.go # Interface definitions
+│ ├── model.go # Model structures
+│ └── response.go # Response data structures
+├── repo # Database operation encapsulation
+│ └── repo.go
+├── resolver # GraphQL resolvers
+│ ├── mutation.go # Mutation resolver
+│ ├── query.go # Query resolver
+│ └── resolver.go # Resolver main entry
+├── schema # GraphQL Schema files
+│ └── user.graphql # Example Schema file
+└── service # Service logic
+ └── service.go
+```
+
+### Next Steps
+
+Add your custom schema files in the `schema` directory, then run the following command to generate corresponding code:
+
+```bash
+lighthouse generate:schema
+```
+
+### Using Directives
+
+In `lighthouse`, you can use the following directives in your GraphQL Schema:
+
+- **@skip / @include**: Conditional query directives for dynamically controlling field inclusion in responses.
+- **@enum**: For defining enum type fields, currently only supports `int8` type.
+- **@paginate / @find / @first**: For pagination, finding, and getting first result queries.
+- **@in / @eq / @neq / @gt / @gte / @lt / @lte / @like / @notIn**: Parameter filtering directives supporting various comparison operators.
+- **@belongsTo / @hasMany / @hasOne / @morphTo / @morphToMany**: Relationship mapping directives for defining model relationships.
+- **@index / @unique**: Create index or add unique constraint for fields.
+- **@defaultString / @defaultInt**: Set default values for fields.
+- **@tag**: For marking additional field attributes.
+- **@model**: Mark type as database model.
+- **@softDeleteModel**: Mark type as database model with soft delete support.
+- **@order**: For sorting query results.
+- **@cache**: For caching query results to improve response speed.
+
+### Example Code
+
+Here's an example query using the `@paginate` directive for user data pagination:
+
+```graphql
+type Query {
+ users: [User] @paginate(scopes: ["active"])
+}
+
+type User @model(name: "UserModel") {
+ id: ID!
+ name: String!
+ age: Int
+ posts: [Post] @hasMany(relation: "Post", foreignKey: "user_id")
+}
+```
-## Status: Under Development 🚧
+## Extension and Customization
-Lighthouse is currently under active development. Stay tuned for exciting updates!
+`lighthouse` provides flexible extension interfaces, you can:
-Coming soon...
+- **Add Custom Directives**: Write your own directives to extend framework functionality.
+- **Support Other ORMs**: Add support for other ORM libraries by referencing the `gorm` integration approach.
+## Development Plan
-## License
+| 🚀 Feature Category | ✨ Feature Description | 📅 Status |
+|-------------------|---------------------|-----------|
+| 🛠️ Custom Directives | Add support for custom directives | ✅ Completed |
+| 📊 Query Directives | Add @find and @first annotations for query support | ✅ Completed |
+| 🔍 Query & Filtering | Add date range filtering directives | ✅ Completed |
+| | Add string matching directives | ✅ Completed |
+| | Add dynamic sorting directives | ✅ Completed |
+| 📊 Pagination | Add @paginate annotation for pagination support | ✅ Completed |
+| 📜 Conditional Query | Add @skip and @include conditional query directives | 🚧 In Progress |
+| 📚 Relationship Mapping | Add @morphTo, @morphToMany, @hasOne, @manyToMany | 🚧 In Progress |
+| 🔧 Microservice Management | Add microservice registry | ⏳ Planned |
+| 💾 Cache Integration | Integrate Redis as cache support | ✅ Completed |
+| 📝 Logging System | Integrate zeroLog system, support Elasticsearch and file logging | ✅ Completed |
+| 🔄 Cache Directive | Add @cache directive to support query result caching | 🚧 In Progress |
+| 🔀 Sorting Directive | Add @order directive to support query result sorting | 🚧 In Progress |
+| 🗄️ ORM Support | Extend support for other ORMs like `ent`, `sqlc` | ⏳ Planned |
+| 📑 Doc Generation | Auto-generate GraphQL Schema documentation | ⏳ Planned |
+| 📦 Plugin Support | Provide plugin system for community contributions | ⏳ Planned |
+| 🌐 Frontend Tools | Develop Apollo Studio-like frontend for query testing | ⏳ Planned |
+| 📊 Performance Tracking | Support performance tracking for fields and services | ⏳ Planned |
-This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
\ No newline at end of file
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 0000000..3c10780
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,156 @@
+# **🚢 Lighthouse GraphQL Framework**
+
+[English](https://github.com/light-speak/lighthouse/blob/main/README.md) | [中文](https://github.com/light-speak/lighthouse/blob/main/README_zh.md)
+
+[![CI](https://github.com/light-speak/lighthouse/actions/workflows/main.yml/badge.svg)](https://github.com/light-speak/lighthouse/actions/workflows/main.yml)
+[![codecov](https://codecov.io/gh/light-speak/lighthouse/branch/main/graph/badge.svg)](https://codecov.io/gh/light-speak/lighthouse)
+[![Go Report Card](https://goreportcard.com/badge/github.com/light-speak/lighthouse)](https://goreportcard.com/report/github.com/light-speak/lighthouse)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+
+`Lighthouse` 是一个功能丰富的自研 GraphQL 框架,旨在简化基于微服务架构的 GraphQL 服务开发。框架集成了日志系统(使用 `zeroLog`),支持 Elasticsearch、文件日志模式和 Redis 缓存,同时具有灵活的指令系统和强大的自定义配置能力。框架目前内置对 `gorm` 的支持,未来还将扩展更多的 ORM 选项。
+
+## 特性
+
+- **微服务架构支持**:采用独立的微服务模式,不支持 GraphQL Federation,而是通过自定义的服务注册中心来管理微服务。
+- **自定义指令**:支持丰富的自定义指令,可以实现动态查询、过滤、关联等操作。
+- **可扩展性**:通过配置文件支持多种 GraphQL 文件结构,灵活满足不同项目需求。
+- **ORM 集成**:目前支持 `gorm`,未来会增加对更多 ORM 库的支持。
+- **日志与缓存集成**:集成了 `zeroLog` 日志系统,支持 Elasticsearch、文件日志和 Redis 缓存。
+
+## 快速开始
+
+### 安装
+
+1. **使用 `go install` 安装**
+
+ ```bash
+ go install github.com/light-speak/lighthouse@latest
+ ```
+
+2. **创建新项目**
+
+ ```bash
+ lighthouse generate:init
+ ```
+
+### 配置文件 (lighthouse.yml)
+
+`lighthouse.yml` 是 `lighthouse` 的核心配置文件,用于指定 GraphQL Schema 路径、文件扩展名、ORM 设置等。以下是示例配置:
+
+```yaml
+# lighthouse.yml
+
+schema:
+ ext:
+ - graphql # 支持的文件扩展名
+ - graphqls
+ path:
+ - schema # GraphQL Schema 文件所在路径
+ model:
+ orm: gorm # ORM 配置,当前支持 gorm
+```
+
+- `schema.ext`:指定 Schema 文件的扩展名,可以是 `.graphql` 或 `.graphqls`。
+- `schema.path`:定义 Schema 文件的路径,框架将自动加载该路径下的所有文件。
+- `model.orm`:当前支持 `gorm` 作为 ORM 库。
+
+### 目录结构
+
+`example` 项目结构如下:
+
+```plaintext
+.
+├── cmd # CLI 相关代码
+│ ├── cmd.go # 主命令入口
+│ ├── migrate
+│ │ └── migrate.go # 数据库迁移逻辑
+│ └── start
+│ └── start.go # 启动服务入口
+├── models # 数据模型相关定义
+│ ├── enum.go # 枚举类型定义
+│ ├── input.go # 输入类型定义
+│ ├── interface.go # 接口定义
+│ ├── model.go # 模型结构
+│ └── response.go # 响应数据结构
+├── repo # 数据库操作封装
+│ └── repo.go
+├── resolver # GraphQL 解析器
+│ ├── mutation.go # Mutation 解析
+│ ├── query.go # Query 解析
+│ └── resolver.go # Resolver 主入口
+├── schema # GraphQL Schema 文件
+│ └── user.graphql # 示例 Schema 文件
+└── service # 服务逻辑
+ └── service.go
+```
+
+### 接下来步骤
+
+在 `schema` 目录下填入自定义的 `schema` 文件,然后执行以下命令生成对应的代码:
+
+lighthouse generate:schema
+
+### 使用指令
+
+在 `lighthouse` 中,你可以在 GraphQL Schema 中使用以下指令:
+
+- **@skip / @include**:条件查询指令,用于动态控制字段是否包含在响应中。
+- **@enum**:用于定义枚举类型字段,目前只支持 `int8` 类型。
+- **@paginate / @find / @first**:用于分页、查找和获取第一个结果的查询。
+- **@in / @eq / @neq / @gt / @gte / @lt / @lte / @like / @notIn**:用于参数过滤的指令,支持各种比较运算符。
+- **@belongsTo / @hasMany / @hasOne / @morphTo / @morphToMany**:关系映射指令,用于定义模型之间的关系。
+- **@index / @unique**:为字段创建索引或添加唯一约束。
+- **@defaultString / @defaultInt**:为字段设置默认值。
+- **@tag**:用于标记字段的附加属性。
+- **@model**:标记类型为数据库模型。
+- **@softDeleteModel**:标记类型为数据库模型,并支持软删除功能。
+- **@order**:用于对查询结果进行排序。
+- **@cache**:用于缓存查询结果,提高响应速度。
+
+### 示例代码
+
+以下是一个示例查询,在获取用户数据时使用了 `@paginate` 指令进行分页:
+
+```graphql
+type Query {
+ users: [User] @paginate(scopes: ["active"])
+}
+
+type User @model(name: "UserModel") {
+ id: ID!
+ name: String!
+ age: Int
+ posts: [Post] @hasMany(relation: "Post", foreignKey: "user_id")
+}
+```
+
+## 扩展与自定义
+
+`lighthouse` 提供了灵活的扩展接口,你可以:
+
+- **添加自定义指令**:编写自己的指令来扩展框架的功能。
+- **支持其他 ORM**:参考 `gorm` 集成方式,添加对其他 ORM 库的支持。
+
+## 开发计划
+
+| 🚀 功能分类 | ✨ 功能描述 | 📅 状态 |
+| -------------- | -------------------------------------------------- | -------- |
+| 🛠️ 自定义指令 | 添加自定义指令的支持 | ✅ 已完成 |
+| 📊 查询指令 | 添加 @find 和 @first 注解以支持查询功能 | ✅ 已完成 |
+| 🔍 查询与过滤 | 添加日期范围过滤指令 | ✅ 已完成 |
+| | 添加字符串匹配指令 | ✅ 已完成 |
+| | 添加动态排序指令 | ✅ 已完成 |
+| 📊 分页指令 | 添加 @paginate 注解以支持分页功能 | ✅ 已完成 |
+| 📜 条件查询指令 | 添加 @skip 和 @include 条件查询指令 | 🚧 进行中 |
+| 📚 关系映射指令 | 添加 @morphTo, @morphToMany, @hasOne, @manyToMany | 🚧 进行中 |
+| 🔧 微服务管理 | 添加微服务注册中心 | ⏳ 计划中 |
+| 💾 缓存集成 | 集成 Redis 作为缓存支持 | ✅ 已完成 |
+| 📝 日志系统 | 集成 zeroLog 日志系统,支持 Elasticsearch 和文件日志 | ✅ 已完成 |
+| 🔄 缓存指令 | 添加 @cache 指令来支持缓存查询结果 | 🚧 进行中 |
+| 🔀 排序指令 | 添加 @order 指令来支持对查询结果进行排序 | 🚧 进行中 |
+| 🗄️ ORM 支持 | 扩展对其他 ORM 的支持,如 `ent`、`sqlc` 等 | ⏳ 计划中 |
+| 📑 文档生成工具 | 自动化生成 GraphQL Schema 文档 | ⏳ 计划中 |
+| 📦 插件支持 | 提供插件系统,支持社区贡献和功能扩展 | ⏳ 计划中 |
+| 🌐 前端工具 | 开发类似 Apollo Studio 的前端,用于查询与测试 | ⏳ 计划中 |
+| 📊 性能追踪 | 支持每个字段和每个服务的性能追踪,以优化 GraphQL 查询性能 | ⏳ 计划中 |
+
diff --git a/example/user/ide-helper.graphql b/example/user/ide-helper.graphql
index ff0e587..147daf1 100644
--- a/example/user/ide-helper.graphql
+++ b/example/user/ide-helper.graphql
@@ -44,3 +44,4 @@ directive @hasMany(relation: String, foreignKey: String, reference: String) on F
directive @hasOne(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION
directive @morphTo(morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
directive @morphToMany(relation: String!, morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION
+directive @manyToMany(relation: String!, pivot: String, foreignKey: String, reference: String) on FIELD_DEFINITION