├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── authcontext └── contextvalues.go ├── authentication ├── authentication.go ├── jwt │ └── jwt.go ├── middleware.go └── remote.go ├── common ├── ability.go ├── role.go └── user.go ├── config ├── config.go ├── env.go ├── env_test.go └── file.go ├── config_example.yaml ├── consts ├── config.go ├── consts.go └── federation.go ├── db ├── dbx.go ├── dialect │ ├── dialect.go │ └── mysql │ │ ├── base.go │ │ ├── delete.go │ │ ├── insert.go │ │ ├── migration.go │ │ ├── query.go │ │ ├── query_test.go │ │ └── update.go └── nulluint64.go ├── go.mod ├── go.sum ├── handler ├── README.md ├── graphiql.go ├── handler.go ├── handlerfunc.go └── playground.go ├── main.go ├── model ├── data │ ├── instance.go │ ├── povit.go │ └── relation.go ├── diff.go ├── domain │ ├── attribute.go │ ├── class.go │ ├── domain.go │ ├── enum.go │ ├── method.go │ └── relation.go ├── graph │ ├── args.go │ ├── args_test.go │ ├── association.go │ ├── attribute.go │ ├── class.go │ ├── entity.go │ ├── enum.go │ ├── external.go │ ├── graph.go │ ├── interface.go │ ├── method.go │ ├── partial.go │ ├── propertier.go │ ├── relation.go │ └── table.go ├── meta │ ├── attributemeta.go │ ├── classmeta.go │ ├── content.go │ ├── meta.go │ ├── methodmeta.go │ ├── predefined.go │ ├── relationmeta.go │ └── type.go ├── model.go └── table │ ├── column.go │ └── table.go ├── repository ├── authorization.go ├── connection.go ├── connectiondo.go ├── convert.go ├── db.go ├── metas.go ├── migrate.go └── repository.go ├── resolve ├── convertid.go ├── file.go ├── install.go ├── loaders.go ├── logout.go ├── me.go ├── middleware.go ├── mutation.go ├── publish.go ├── query.go └── verifier.go ├── scalars ├── federation.go ├── json.go └── upload.go ├── schema ├── aggregate.go ├── cache.go ├── comparison.go ├── enum.go ├── federation.go ├── federationmutation.go ├── federationquery.go ├── globals.go ├── input.go ├── interface.go ├── model.go ├── mutation.go ├── object.go ├── orderby.go ├── property.go ├── query.go ├── queryargs.go ├── relation.go ├── resolve.go ├── schema.go ├── service.go ├── subscription.go └── upload.go ├── sdlview.html ├── storage └── file.go ├── utils ├── array.go ├── debug.go ├── id.go ├── json.go ├── map.go ├── object.go └── string.go └── 备忘.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | config.yaml 15 | uploads 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-buster as builder 2 | 3 | ENV GOPROXY=https://goproxy.cn,direct 4 | ENV GO111MODULE=on 5 | ENV GOFLAGS=-mod=vendor 6 | ENV APP_HOME /go/src/entify 7 | 8 | WORKDIR "$APP_HOME" 9 | ADD . "$APP_HOME" 10 | 11 | RUN go mod download 12 | RUN go mod verify 13 | RUN go get ./... 14 | RUN go mod vendor 15 | RUN go build -o entify 16 | 17 | EXPOSE 4000 18 | CMD ["./entify"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Water.Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # entify 2 | GraphQL low code engine, suport micro-service 3 | -------------------------------------------------------------------------------- /authcontext/contextvalues.go: -------------------------------------------------------------------------------- 1 | package authcontext 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/common" 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | type ContextValues struct { 10 | Token string 11 | Me *common.User 12 | QueryUserCache map[string][]string 13 | } 14 | 15 | func ParseContextValues(p graphql.ResolveParams) ContextValues { 16 | values := p.Context.Value(consts.CONTEXT_VALUES) 17 | if values == nil { 18 | panic("Not set CONTEXT_VALUES in context") 19 | } 20 | 21 | return values.(ContextValues) 22 | } 23 | -------------------------------------------------------------------------------- /authentication/authentication.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | 8 | "golang.org/x/crypto/bcrypt" 9 | "rxdrag.com/entify/authentication/jwt" 10 | "rxdrag.com/entify/common" 11 | "rxdrag.com/entify/config" 12 | "rxdrag.com/entify/db/dialect" 13 | "rxdrag.com/entify/repository" 14 | ) 15 | 16 | var TokenCache = map[string]*common.User{} 17 | 18 | func loadUser(loginName string) *common.User { 19 | con, err := repository.Open(repository.NewSupperVerifier()) 20 | if err != nil { 21 | fmt.Println(err) 22 | panic(err) 23 | } 24 | var user common.User 25 | var isSupper sql.NullBool 26 | var isDemo sql.NullBool 27 | 28 | sqlBuilder := dialect.GetSQLBuilder() 29 | err = con.Dbx.QueryRow(sqlBuilder.BuildMeSQL(), loginName).Scan( 30 | &user.Id, 31 | &user.Name, 32 | &user.LoginName, 33 | &isSupper, 34 | &isDemo, 35 | ) 36 | switch { 37 | case err == sql.ErrNoRows: 38 | return nil 39 | case err != nil: 40 | panic(err.Error()) 41 | } 42 | 43 | user.IsSupper = isSupper.Bool 44 | user.IsDemo = isDemo.Bool 45 | 46 | rows, err := con.Dbx.Query(sqlBuilder.BuildRolesSQL(), user.Id) 47 | if err != nil { 48 | panic(err.Error()) 49 | } 50 | for rows.Next() { 51 | var role common.Role 52 | err = rows.Scan(&role.Id, &role.Name) 53 | if err != nil { 54 | panic(err.Error()) 55 | } 56 | user.Roles = append(user.Roles, role) 57 | } 58 | return &user 59 | } 60 | 61 | func Login(loginName, pwd string) (string, error) { 62 | con, err := repository.Open(repository.NewSupperVerifier()) 63 | if err != nil { 64 | fmt.Println(err) 65 | panic(err) 66 | } 67 | sqlBuilder := dialect.GetSQLBuilder() 68 | var password string 69 | err = con.Dbx.QueryRow(sqlBuilder.BuildLoginSQL(), loginName).Scan(&password) 70 | if err != nil { 71 | fmt.Println(err) 72 | return "", errors.New("Login failed!") 73 | } 74 | 75 | err = bcrypt.CompareHashAndPassword([]byte(password), []byte(pwd)) //验证(对比) 76 | if err != nil { 77 | fmt.Println(err, pwd, password) 78 | return "", errors.New("Password error!") 79 | } 80 | 81 | token, err := jwt.GenerateToken(loginName) 82 | if err != nil { 83 | panic(err.Error()) 84 | } 85 | 86 | user := loadUser(loginName) 87 | TokenCache[token] = user 88 | return token, err 89 | } 90 | 91 | func Logout(token string) { 92 | TokenCache[token] = nil 93 | } 94 | 95 | func GetUserByToken(token string) (*common.User, error) { 96 | authUrl := config.AuthUrl() 97 | if authUrl == "" { 98 | return TokenCache[token], nil 99 | } else { 100 | return meFromRemote(token) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /authentication/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/dgrijalva/jwt-go" 8 | ) 9 | 10 | // secret key being used to sign tokens 11 | var ( 12 | SecretKey = []byte("secret") 13 | ) 14 | 15 | // GenerateToken generates a jwt token and assign a username to it's claims and return it 16 | func GenerateToken(username string) (string, error) { 17 | token := jwt.New(jwt.SigningMethodHS256) 18 | /* Create a map to store our claims */ 19 | claims := token.Claims.(jwt.MapClaims) 20 | /* Set token claims */ 21 | claims["username"] = username 22 | claims["exp"] = time.Now().Add(time.Hour * 24).Unix() 23 | tokenString, err := token.SignedString(SecretKey) 24 | if err != nil { 25 | log.Fatal("Error in Generating key") 26 | return "", err 27 | } 28 | return tokenString, nil 29 | } 30 | 31 | // ParseToken parses a jwt token and returns the username in it's claims 32 | func ParseToken(tokenStr string) (string, error) { 33 | token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { 34 | return SecretKey, nil 35 | }) 36 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 37 | username := claims["username"].(string) 38 | return username, nil 39 | } else { 40 | return "", err 41 | } 42 | } 43 | 44 | // 原文链接:https://www.howtographql.com/graphql-go/6-authentication/ 45 | -------------------------------------------------------------------------------- /authentication/middleware.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "rxdrag.com/entify/authcontext" 10 | "rxdrag.com/entify/consts" 11 | ) 12 | 13 | // AuthMiddleware 传递公共参数中间件 14 | func AuthMiddleware(next http.Handler) http.Handler { 15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | //为了测试loading状态,生产版需要删掉 17 | time.Sleep(time.Duration(300) * time.Millisecond) 18 | 19 | reqToken := r.Header.Get(consts.AUTHORIZATION) 20 | splitToken := strings.Split(reqToken, consts.BEARER) 21 | v := authcontext.ContextValues{} 22 | if len(splitToken) == 2 { 23 | reqToken = splitToken[1] 24 | if reqToken != "" { 25 | v.Token = reqToken 26 | me, err := GetUserByToken(reqToken) 27 | if err != nil { 28 | http.Error(w, err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | v.Me = me 32 | } 33 | } 34 | ctx := context.WithValue(r.Context(), consts.CONTEXT_VALUES, v) 35 | ctx = context.WithValue(ctx, consts.HOST, r.Host) 36 | next.ServeHTTP(w, r.WithContext(ctx)) 37 | }) 38 | } 39 | 40 | func CorsMiddleware(next http.Handler) http.Handler { 41 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 | //设置跨域 43 | w.Header().Set("Access-Control-Allow-Origin", "*") 44 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 45 | w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") 46 | //w.Header().Set("Access-Control-Allow-Credentials", "true") 47 | next.ServeHTTP(w, r) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /authentication/remote.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/mitchellh/mapstructure" 13 | "rxdrag.com/entify/common" 14 | "rxdrag.com/entify/config" 15 | "rxdrag.com/entify/consts" 16 | ) 17 | 18 | func meFromRemote(token string) (*common.User, error) { 19 | authUrl := config.AuthUrl() 20 | jsonData := map[string]string{ 21 | "query": ` 22 | { 23 | me { 24 | id 25 | name 26 | loginName 27 | isSupper 28 | isDemo 29 | roles { 30 | id 31 | name 32 | } 33 | } 34 | } 35 | `, 36 | } 37 | jsonValue, _ := json.Marshal(jsonData) 38 | request, err := http.NewRequest("POST", authUrl, bytes.NewBuffer(jsonValue)) 39 | request.Header.Set(consts.AUTHORIZATION, consts.BEARER+token) 40 | client := &http.Client{Timeout: time.Second * 10} 41 | response, err := client.Do(request) 42 | if err != nil { 43 | fmt.Printf("The HTTP request failed with error %s\n", err) 44 | return nil, errors.New("Can't access authentication service: " + authUrl) 45 | } 46 | defer response.Body.Close() 47 | 48 | data, _ := ioutil.ReadAll(response.Body) 49 | var user common.User 50 | var userJson map[string]interface{} 51 | json.Unmarshal(data, &userJson) 52 | fmt.Println(userJson) 53 | if userJson["data"] != nil { 54 | meJson := userJson["data"].(map[string]interface{})["me"] 55 | if meJson != nil { 56 | err = mapstructure.Decode(meJson, &user) 57 | if err != nil { 58 | panic(err.Error()) 59 | } 60 | } 61 | return &user, nil 62 | } 63 | return nil, nil 64 | } 65 | -------------------------------------------------------------------------------- /common/ability.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Ability struct { 4 | Id uint64 `json:"id"` 5 | EntityUuid string `json:"entityUuid"` 6 | ColumnUuid string `json:"columnUuid"` 7 | Can bool `json:"can"` 8 | Expression string `json:"expression"` 9 | AbilityType string `json:"abilityType"` 10 | RoleId uint64 `json:"roleId"` 11 | } 12 | -------------------------------------------------------------------------------- /common/role.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "strconv" 4 | 5 | type Role struct { 6 | Id string `json:"id"` 7 | Name string `json:"name"` 8 | } 9 | 10 | func (r Role) Uint64Id() uint64 { 11 | number, err := strconv.ParseUint(r.Id, 10, 64) 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | return number 16 | } 17 | -------------------------------------------------------------------------------- /common/user.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "strconv" 4 | 5 | type User struct { 6 | Id string `json:"id"` //ID类型,查询时需要用字符串接收 7 | Name string `json:"name"` 8 | LoginName string `json:"loginName"` 9 | Roles []Role `json:"roles"` 10 | IsSupper bool `json:"isSupper"` 11 | IsDemo bool `json:"isDemo"` 12 | } 13 | 14 | func (u User) Uint64Id() uint64 { 15 | number, err := strconv.ParseUint(u.Id, 10, 64) 16 | if err != nil { 17 | panic(err.Error()) 18 | } 19 | return number 20 | } 21 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | const TABLE_NAME_MAX_LENGTH = 64 10 | 11 | var fileCfg Config 12 | var envCfg Config 13 | 14 | type DbConfig struct { 15 | Driver string `json:"driver"` 16 | User string `json:"user"` 17 | Password string `json:"password"` 18 | Host string `json:"host"` 19 | Port string `json:"port"` 20 | Database string `json:"database"` 21 | } 22 | 23 | type Config interface { 24 | getString(key string) string 25 | getBool(key string) bool 26 | getInt(key string) int 27 | } 28 | 29 | func GetString(key string) string { 30 | str := envCfg.getString(key) 31 | if str == "" { 32 | str = fileCfg.getString(key) 33 | } 34 | return str 35 | } 36 | 37 | func GetBool(key string) bool { 38 | boolValue := envCfg.getBool(key) 39 | if !boolValue { 40 | boolValue = fileCfg.getBool(key) 41 | } 42 | return boolValue 43 | } 44 | 45 | func GetInt(key string) int { 46 | intValue := envCfg.getInt(key) 47 | if intValue == 0 { 48 | intValue = fileCfg.getInt(key) 49 | } 50 | return intValue 51 | } 52 | 53 | func GetDbConfig() DbConfig { 54 | var cfg DbConfig 55 | cfg.Driver = GetString(consts.DB_DRIVER) 56 | cfg.Database = GetString(consts.DB_DATABASE) 57 | cfg.Host = GetString(consts.DB_HOST) 58 | cfg.Port = GetString(consts.DB_PORT) 59 | cfg.User = GetString(consts.DB_USER) 60 | cfg.Password = GetString(consts.DB_PASSWORD) 61 | if cfg.Driver == "" { 62 | cfg.Driver = "mysql" 63 | } 64 | return cfg 65 | } 66 | 67 | func ServiceId() int { 68 | serviceId := GetInt(consts.SERVICE_ID) 69 | if serviceId == 0 { 70 | fmt.Println("Not set service id, use 1 as service id") 71 | return 1 72 | } 73 | return serviceId 74 | } 75 | 76 | func AuthUrl() string { 77 | return GetString(consts.AUTH_URL) 78 | } 79 | 80 | func Storage() string { 81 | return GetString(consts.STORAGE) 82 | } 83 | 84 | func init() { 85 | fileCfg = newFileConfig() 86 | envCfg = newEnvConfig() 87 | } 88 | -------------------------------------------------------------------------------- /config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/spf13/viper" 7 | "rxdrag.com/entify/consts" 8 | ) 9 | 10 | type EnvConfig struct { 11 | v *viper.Viper 12 | } 13 | 14 | const ( 15 | TRUE = "true" 16 | FALSE = "false" 17 | ) 18 | 19 | func newEnvConfig() *EnvConfig { 20 | var e EnvConfig 21 | e.v = viper.New() 22 | e.v.BindEnv(consts.DB_USER) 23 | e.v.BindEnv(consts.DB_DRIVER) 24 | e.v.BindEnv(consts.DB_PASSWORD) 25 | e.v.BindEnv(consts.DB_HOST) 26 | e.v.BindEnv(consts.DB_PORT) 27 | e.v.BindEnv(consts.DB_DATABASE) 28 | e.v.BindEnv(consts.SERVICE_ID) 29 | e.v.BindEnv(consts.AUTH_URL) 30 | e.v.BindEnv(consts.STORAGE) 31 | return &e 32 | } 33 | 34 | func (e *EnvConfig) getString(key string) string { 35 | str := e.v.Get(key) 36 | if str != nil { 37 | return str.(string) 38 | } 39 | return "" 40 | } 41 | 42 | func (e *EnvConfig) getBool(key string) bool { 43 | return e.v.Get(key) == TRUE 44 | } 45 | 46 | func (e *EnvConfig) getInt(key string) int { 47 | value := e.getString(key) 48 | if value == "" { 49 | return 0 50 | } 51 | i, err := strconv.ParseInt(value, 0, 32) 52 | if err != nil { 53 | return int(i) 54 | } 55 | return 0 56 | } 57 | -------------------------------------------------------------------------------- /config/env_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | func TestGetString(t *testing.T) { 10 | if GetString(consts.DB_DRIVER) != "mysql" { 11 | t.Error("Getstring Error:" + GetString(consts.DB_DRIVER)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/file.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | "rxdrag.com/entify/consts" 6 | ) 7 | 8 | const ( 9 | PATH = "." 10 | CONFIG_TYPE = "yaml" 11 | CONFIG_NAME = "config" 12 | ) 13 | 14 | type FileConfig struct { 15 | v *viper.Viper 16 | } 17 | 18 | func newFileConfig() *FileConfig { 19 | var f FileConfig 20 | f.v = viper.New() 21 | f.v.SetConfigName(CONFIG_NAME) // name of config file (without extension) 22 | f.v.SetConfigType(CONFIG_TYPE) // REQUIRED if the config file does not have the extension in the name 23 | f.v.AddConfigPath(PATH) 24 | 25 | err := f.v.ReadInConfig() // Find and read the config file 26 | if err != nil { 27 | panic("Env not set and can not find config file") 28 | } 29 | 30 | f.v.SetDefault(consts.SERVICE_ID, 1) 31 | f.v.SetDefault(consts.DB_DRIVER, "mysql") 32 | return &f 33 | } 34 | 35 | func (f *FileConfig) getString(key string) string { 36 | return f.v.GetString(key) 37 | } 38 | 39 | func (f *FileConfig) getBool(key string) bool { 40 | return f.v.GetBool(key) 41 | } 42 | 43 | func (f *FileConfig) getInt(key string) int { 44 | return f.v.GetInt(key) 45 | } 46 | -------------------------------------------------------------------------------- /config_example.yaml: -------------------------------------------------------------------------------- 1 | database: database_name 2 | driver: mysql 3 | host: localhost_name 4 | service_id: 1 5 | password: your password 6 | port: "3306" 7 | user: root 8 | auth_url: "" 9 | #storage: "local" -------------------------------------------------------------------------------- /consts/config.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const ( 4 | CONFIG_PREFIX = "entify" 5 | DB_DRIVER = "driver" 6 | DB_USER = "user" 7 | DB_PASSWORD = "password" 8 | DB_HOST = "host" 9 | DB_PORT = "port" 10 | DB_DATABASE = "database" 11 | SERVICE_ID = "service_id" 12 | AUTH_URL = "auth_url" 13 | STORAGE = "storage" 14 | UPLOAD_PATH = "./static/uploads" 15 | UPLOAD_PRIFIX = "/uploads" 16 | ) 17 | 18 | const ( 19 | LOCAL = "local" 20 | ALIYUN = "aliyun" 21 | ) 22 | 23 | const ( 24 | URL = "url" 25 | ADMIN = "admin" 26 | ADMINPASSWORD = "password" 27 | WITHDEMO = "withDemo" 28 | ) 29 | 30 | const ( 31 | SERVICE_NODE_TYPE = "serviceType" 32 | NORMAL_SERVICE = "normal" 33 | AUTH_SERVICE = "auth" 34 | ) 35 | -------------------------------------------------------------------------------- /consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const LOADERS = "loaders" 4 | 5 | const ( 6 | ROOT_QUERY_NAME = "Query" 7 | ROOT_MUTATION_NAME = "Mutation" 8 | ROOT_SUBSCRIPTION_NAME = "Subscription" 9 | LOGIN = "login" 10 | LOGIN_NAME = "loginName" 11 | PASSWORD = "password" 12 | LOGOUT = "logout" 13 | ME = "me" 14 | IS_SUPPER = "isSupper" 15 | IS_DEMO = "isDemo" 16 | PUBLISH = "publish" 17 | ROLLBACK = "rollback" 18 | SYNC_META = "syncMeta" 19 | NAME = "name" 20 | INSTALLED = "installed" 21 | CAN_UPLOAD = "canUpload" 22 | 23 | ONE = "one" 24 | QUERY = "query" 25 | AGGREGATE = "aggregate" 26 | FIELDS = "Fields" 27 | NODES = "nodes" 28 | INPUT = "Input" 29 | SET_INPUT = "Set" 30 | UPSERT = "upsert" 31 | UPSERT_ONE = "upsertOne" 32 | INSERT = "insert" 33 | INSERT_ONE = "insertOne" 34 | UPDATE = "update" 35 | UPDATE_ONE = "updateOne" 36 | DELETE = "delete" 37 | BY_ID = "ById" 38 | SET = "set" 39 | HAS_MANY = "HasMany" 40 | HAS_ONE = "HasOne" 41 | ENTITY = "Entity" 42 | 43 | ARG_DISTINCTON string = "distinctOn" 44 | ARG_LIMIT string = "limit" 45 | ARG_OFFSET string = "offset" 46 | ARG_ORDERBY string = "orderBy" 47 | ARG_WHERE string = "where" 48 | 49 | ARG_ADD string = "add" 50 | ARG_DELETE string = "delete" 51 | ARG_UPDATE string = "update" 52 | ARG_SYNC string = "sync" 53 | ARG_CASCADE string = "cascade" 54 | 55 | ARG_AND string = "_and" 56 | ARG_NOT string = "_not" 57 | ARG_OR string = "_or" 58 | ) 59 | 60 | //EQ("="), GTE(">="), GT(">"), LT("<"), LTE("<="); 61 | const ( 62 | ARG_EQ string = "_eq" 63 | ARG_GT string = "_gt" 64 | ARG_GTE string = "_gte" 65 | ARG_IN string = "_in" 66 | ARG_ISNULL string = "_isNull" 67 | ARG_LT string = "_lt" 68 | ARG_LTE string = "_lte" 69 | ARG_NOTEQ string = "_notEq" 70 | ARG_NOTIN string = "_notIn" 71 | 72 | ARG_ILIKE string = "_iLike" 73 | // ARG_IREGEX string = "_iregex" 74 | ARG_LIKE string = "_like" 75 | ARG_NOTILIKE string = "_notILike" 76 | // ARG_NOTIREGEX string = "_notIRegex" 77 | ARG_NOTLIKE string = "_notLike" 78 | ARG_NOTREGEX string = "_notRegexp" 79 | // ARG_NOTSIMILAR string = "_notSimilar" 80 | ARG_REGEX string = "_regexp" 81 | // ARG_SIMILAR string = "_similar" 82 | ) 83 | 84 | const ( 85 | ARG_COUNT string = "count" 86 | ARG_COLUMNS string = "columns" 87 | ARG_DISTINCT string = "distinct" 88 | ) 89 | 90 | const ( 91 | ARG_OBJECT string = "object" 92 | ARG_OBJECTS string = "objects" 93 | RESPONSE_RETURNING string = "returning" 94 | RESPONSE_AFFECTEDROWS string = "affectedRows" 95 | ARG_SET string = "set" 96 | ARG_FILE string = "file" 97 | ) 98 | 99 | const ( 100 | UUID string = "uuid" 101 | INNERID string = "innerId" 102 | TYPE string = "type" 103 | ) 104 | 105 | /** 106 | * Meta实体用到的常量 107 | **/ 108 | const ( 109 | META_ENTITY_NAME string = "Meta" 110 | META_ID string = "id" 111 | META_STATUS string = "status" 112 | META_CONTENT string = "content" 113 | META_PUBLISHEDAT string = "publishedAt" 114 | META_CREATEDAT string = "createdAt" 115 | META_UPDATEDAT string = "updatedAt" 116 | 117 | META_CLASSES string = "classes" 118 | META_RELATIONS string = "relations" 119 | ) 120 | 121 | const ( 122 | MEDIA_ENTITY_NAME = "Media" 123 | MEDIA_UUID = "MEDIA_ENTITY_UUID" 124 | ) 125 | 126 | const ( 127 | ID_SUFFIX string = "_id" 128 | PIVOT string = "pivot" 129 | INDEX_SUFFIX string = "_idx" 130 | SUFFIX_SOURCE string = "_source" 131 | SUFFIX_TARGET string = "_target" 132 | ) 133 | 134 | const ( 135 | ID string = "id" 136 | OF string = "Of" 137 | ) 138 | 139 | const ( 140 | DELETED_AT string = "deletedAt" 141 | ) 142 | 143 | const ( 144 | BOOLEXP string = "BoolExp" 145 | ORDERBY string = "OrderBy" 146 | DISTINCTEXP string = "DistinctExp" 147 | MUTATION_RESPONSE string = "MutationResponse" 148 | ) 149 | 150 | const ASSOCIATION_OWNER_ID = "owner__rx__id" 151 | 152 | const META_USER = "User" 153 | const META_ROLE = "Role" 154 | 155 | const SYSTEM = "System" 156 | const CREATEDATE = "createDate" 157 | const UPDATEDATE = "updateDate" 158 | 159 | const ( 160 | TOKEN = "token" 161 | AUTHORIZATION = "Authorization" 162 | BEARER = "Bearer " 163 | CONTEXT_VALUES = "values" 164 | HOST = "host" 165 | ) 166 | 167 | const ( 168 | ABILITY_UUID = "META_ABILITY_UUID" 169 | USER_UUID = "META_USER_UUID" 170 | ROLE_UUID = "META_ROLE_UUID" 171 | ) 172 | const ( 173 | META_INNER_ID = 1 174 | ENTITY_AUTH_SETTINGS_INNER_ID = 2 175 | Ability_INNER_ID = 3 176 | USER_INNER_ID = 4 177 | ROLE_INNER_ID = 5 178 | MEDIA_INNER_ID = 6 179 | ROLE_USER_RELATION_INNER_ID = 101 180 | ) 181 | 182 | const ROOT = "root" 183 | 184 | //普通角色的ID永远不会是1 185 | const GUEST_ROLE_ID = "1" 186 | const PREDEFINED_QUERYUSER = "$queryUser" 187 | const PREDEFINED_ME = "$me" 188 | 189 | const NO_PERMISSION = "No permission to access data" 190 | 191 | const ( 192 | FILE = "File" 193 | FILE_NAME = "fileName" 194 | FILE_SIZE = "size" 195 | FILE_MIMETYPE = "mimeType" 196 | FILE_URL = "url" 197 | File_EXTNAME = "extName" 198 | FILE_THMUBNAIL = "thumbnail" 199 | FILE_RESIZE = "resize" 200 | FILE_WIDTH = "width" 201 | FILE_HEIGHT = "height" 202 | ) 203 | -------------------------------------------------------------------------------- /consts/federation.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const ( 4 | SCALAR_ANY = "_Any" 5 | SCALAR_FIELDSET = "_FieldSet" 6 | SERVICE_TYPE = "_Service" 7 | ENTITY_TYPE = "_Entity" 8 | SERVICE = "_service" 9 | ENTITIES = "_entities" 10 | SDL = "sdl" 11 | REPRESENTATIONS = "representations" 12 | TYPENAME = "__typename" 13 | ) 14 | 15 | const ( 16 | EXTERNAL = "external" 17 | REQUIRES = "requires" 18 | PROVIDES = "provides" 19 | KEY = "key" 20 | EXTENDS = "extends" 21 | ARG_FIELDS = "fields" 22 | ) 23 | -------------------------------------------------------------------------------- /db/dbx.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | ) 7 | 8 | var openedDB *sql.DB 9 | 10 | type Dbx struct { 11 | db *sql.DB 12 | tx *sql.Tx 13 | } 14 | 15 | func (d *Dbx) BeginTx() error { 16 | tx, err := d.db.Begin() 17 | if err != nil { 18 | return err 19 | } 20 | 21 | d.tx = tx 22 | return nil 23 | } 24 | 25 | func (d *Dbx) ClearTx() { 26 | d.validateTx() 27 | err := d.Rollback() 28 | if err != sql.ErrTxDone && err != nil { 29 | log.Fatalln(err) 30 | } 31 | } 32 | 33 | func (d *Dbx) validateDb() { 34 | if d.db == nil { 35 | panic("Not init connection with db") 36 | } 37 | } 38 | 39 | func (d *Dbx) validateTx() { 40 | if d.tx == nil { 41 | panic("Not init connection with tx") 42 | } 43 | } 44 | 45 | func (d *Dbx) Exec(sql string, args ...interface{}) (sql.Result, error) { 46 | d.validateDb() 47 | if d.tx != nil { 48 | return d.tx.Exec(sql, args...) 49 | } 50 | return d.db.Exec(sql, args...) 51 | } 52 | 53 | func (d *Dbx) Query(query string, args ...interface{}) (*sql.Rows, error) { 54 | d.validateDb() 55 | if d.tx != nil { 56 | return d.tx.Query(query, args...) 57 | } else { 58 | return d.db.Query(query, args...) 59 | } 60 | } 61 | 62 | func (d *Dbx) QueryRow(query string, args ...interface{}) *sql.Row { 63 | d.validateDb() 64 | if d.tx != nil { 65 | return d.tx.QueryRow(query, args...) 66 | } else { 67 | return d.db.QueryRow(query, args...) 68 | } 69 | } 70 | 71 | // func (c *Dbx) Close() error { 72 | // c.validateDb() 73 | // return c.db.Close() 74 | // } 75 | 76 | func (d *Dbx) Commit() error { 77 | d.validateTx() 78 | return d.tx.Commit() 79 | } 80 | func (c *Dbx) Rollback() error { 81 | c.validateTx() 82 | return c.tx.Rollback() 83 | } 84 | 85 | func Open(driver string, config string) (*Dbx, error) { 86 | if openedDB == nil { 87 | db, err := sql.Open(driver, config) 88 | openedDB = db 89 | if err != nil { 90 | return nil, err 91 | } 92 | } 93 | con := Dbx{ 94 | db: openedDB, 95 | } 96 | return &con, nil 97 | } 98 | 99 | func Close() error { 100 | if openedDB != nil { 101 | err := openedDB.Close() 102 | openedDB = nil 103 | return err 104 | } 105 | 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /db/dialect/dialect.go: -------------------------------------------------------------------------------- 1 | package dialect 2 | 3 | import ( 4 | "rxdrag.com/entify/db/dialect/mysql" 5 | "rxdrag.com/entify/model" 6 | "rxdrag.com/entify/model/data" 7 | "rxdrag.com/entify/model/graph" 8 | "rxdrag.com/entify/model/table" 9 | ) 10 | 11 | const ( 12 | MySQL = "mysql" 13 | ) 14 | 15 | type SQLBuilder interface { 16 | BuildMeSQL() string 17 | BuildRolesSQL() string 18 | BuildLoginSQL() string 19 | BuildCreateMetaSQL() string 20 | BuildCreateAbilitySQL() string 21 | BuildCreateEntityAuthSettingsSQL() string 22 | BuildBoolExp(argEntity *graph.ArgEntity, where map[string]interface{}) (string, []interface{}) 23 | BuildFieldExp(fieldName string, fieldArgs map[string]interface{}) (string, []interface{}) 24 | 25 | BuildCreateTableSQL(table *table.Table) string 26 | BuildDeleteTableSQL(table *table.Table) string 27 | BuildColumnSQL(column *table.Column) string 28 | BuildModifyTableAtoms(diff *model.TableDiff) []model.ModifyAtom 29 | ColumnTypeSQL(column *table.Column) string 30 | 31 | BuildQuerySQLBody(argEntity *graph.ArgEntity, fields []*graph.Attribute) string 32 | BuildWhereSQL(argEntity *graph.ArgEntity, fields []*graph.Attribute, where map[string]interface{}) (string, []interface{}) 33 | BuildOrderBySQL(argEntity *graph.ArgEntity, orderBy interface{}) string 34 | //BuildQuerySQL(tableName string, fields []*graph.Attribute, args map[string]interface{}) (string, []interface{}) 35 | 36 | BuildInsertSQL(fields []*data.Field, table *table.Table) string 37 | BuildUpdateSQL(id uint64, fields []*data.Field, table *table.Table) string 38 | 39 | BuildQueryByIdsSQL(entity *graph.Entity, idCounts int) string 40 | BuildClearAssociationSQL(ownerId uint64, tableName string, ownerFieldName string) string 41 | BuildQueryAssociatedInstancesSQL(entity *graph.Entity, 42 | ownerId uint64, 43 | povitTableName string, 44 | ownerFieldName string, 45 | typeFieldName string, 46 | ) string 47 | BuildBatchAssociationBodySQL( 48 | argEntity *graph.ArgEntity, 49 | fields []*graph.Attribute, 50 | povitTableName string, 51 | ownerFieldName string, 52 | typeFieldName string, 53 | ids []uint64, 54 | ) string 55 | BuildDeleteSQL(id uint64, tableName string) string 56 | BuildSoftDeleteSQL(id uint64, tableName string) string 57 | 58 | BuildQueryPovitSQL(povit *data.AssociationPovit) string 59 | BuildInsertPovitSQL(povit *data.AssociationPovit) string 60 | BuildDeletePovitSQL(povit *data.AssociationPovit) string 61 | 62 | BuildTableCheckSQL(name string, database string) string 63 | } 64 | 65 | func GetSQLBuilder() SQLBuilder { 66 | var builder mysql.MySQLBuilder 67 | return &builder 68 | } 69 | -------------------------------------------------------------------------------- /db/dialect/mysql/base.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | func (*MySQLBuilder) BuildMeSQL() string { 10 | return "select id, name, loginName, isSupper, isDemo from user where loginName = ?" 11 | } 12 | 13 | func (*MySQLBuilder) BuildRolesSQL() string { 14 | povit := fmt.Sprintf( 15 | "%s_%d_%d_%d", 16 | consts.PIVOT, 17 | consts.ROLE_INNER_ID, 18 | consts.ROLE_USER_RELATION_INNER_ID, 19 | consts.USER_INNER_ID, 20 | ) 21 | return fmt.Sprintf("select a.id, a.name from role a left join %s b on a.id = b.role where b.user = ?", povit) 22 | } 23 | 24 | func (*MySQLBuilder) BuildLoginSQL() string { 25 | return "select password from user where loginName = ?" 26 | } 27 | 28 | func (*MySQLBuilder) BuildCreateMetaSQL() string { 29 | return `CREATE TABLE meta ( 30 | id bigint NOT NULL AUTO_INCREMENT, 31 | content json DEFAULT NULL, 32 | publishedAt datetime DEFAULT NULL, 33 | createdAt datetime DEFAULT NULL, 34 | updatedAt datetime DEFAULT NULL, 35 | status varchar(45) DEFAULT NULL, 36 | PRIMARY KEY (id) 37 | ) ENGINE=InnoDB AUTO_INCREMENT=1507236403010867251 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 38 | ` 39 | } 40 | func (*MySQLBuilder) BuildCreateAbilitySQL() string { 41 | return `CREATE TABLE ability ( 42 | id bigint NOT NULL AUTO_INCREMENT, 43 | entityUuid varchar(255) NOT NULL, 44 | columnUuid varchar(255) DEFAULT NULL, 45 | can tinyint(1) NOT NULL, 46 | expression text NOT NULL, 47 | abilityType varchar(128) NOT NULL, 48 | roleId bigint NOT NULL, 49 | PRIMARY KEY (id) 50 | ) ENGINE=InnoDB AUTO_INCREMENT=4503621102206976 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 51 | ` 52 | } 53 | func (*MySQLBuilder) BuildCreateEntityAuthSettingsSQL() string { 54 | return `CREATE TABLE entity_auth_settings ( 55 | id bigint NOT NULL AUTO_INCREMENT, 56 | entityUuid varchar(255) NOT NULL, 57 | expand tinyint(1) NOT NULL, 58 | PRIMARY KEY (id), 59 | UNIQUE KEY entityUuid_UNIQUE (entityUuid) 60 | ) ENGINE=InnoDB AUTO_INCREMENT=4503616807239680 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 61 | ` 62 | } 63 | 64 | func (b *MySQLBuilder) BuildTableCheckSQL(name string, database string) string { 65 | return fmt.Sprintf( 66 | "SELECT COUNT(*) FROM information_schema.TABLES WHERE table_name ='%s' AND table_schema ='%s'", 67 | name, 68 | database, 69 | ) 70 | } 71 | 72 | func nullableString(nullable bool) string { 73 | if nullable { 74 | return " NULL " 75 | } 76 | return " NOT NULL " 77 | } 78 | -------------------------------------------------------------------------------- /db/dialect/mysql/delete.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "rxdrag.com/entify/consts" 8 | "rxdrag.com/entify/model/data" 9 | ) 10 | 11 | func (b *MySQLBuilder) BuildDeleteSQL(id uint64, tableName string) string { 12 | sql := fmt.Sprintf( 13 | "DELETE FROM `%s` WHERE (`%s` = '%d')", 14 | tableName, 15 | "id", 16 | id, 17 | ) 18 | return sql 19 | } 20 | 21 | func (b *MySQLBuilder) BuildSoftDeleteSQL(id uint64, tableName string) string { 22 | sql := fmt.Sprintf( 23 | "UPDATE `%s` SET `%s` = '%s' WHERE (`%s` = %d)", 24 | tableName, 25 | consts.DELETED_AT, 26 | time.Now(), 27 | "id", 28 | id, 29 | ) 30 | return sql 31 | } 32 | 33 | func (b *MySQLBuilder) BuildDeletePovitSQL(povit *data.AssociationPovit) string { 34 | return fmt.Sprintf( 35 | "DELETE FROM `%s` WHERE (`%s` = %d AND `%s` = %d)", 36 | povit.Table().Name, 37 | povit.Source.Column.Name, 38 | povit.Source.Value, 39 | povit.Target.Column.Name, 40 | povit.Target.Value, 41 | ) 42 | } 43 | 44 | func (b *MySQLBuilder) BuildClearAssociationSQL(ownerId uint64, tableName string, ownerFieldName string) string { 45 | sql := fmt.Sprintf( 46 | "DELETE FROM `%s` WHERE (`%s` = '%d')", 47 | tableName, 48 | ownerFieldName, 49 | ownerId, 50 | ) 51 | return sql 52 | } 53 | -------------------------------------------------------------------------------- /db/dialect/mysql/insert.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "rxdrag.com/entify/model/data" 8 | "rxdrag.com/entify/model/table" 9 | ) 10 | 11 | func (b *MySQLBuilder) BuildInsertSQL(fields []*data.Field, table *table.Table) string { 12 | sql := fmt.Sprintf("INSERT INTO `%s`(%s) VALUES(%s)", table.Name, insertFields(fields), insertValueSymbols(fields)) 13 | 14 | return sql 15 | } 16 | 17 | func (b *MySQLBuilder) BuildInsertPovitSQL(povit *data.AssociationPovit) string { 18 | return fmt.Sprintf( 19 | "INSERT INTO `%s`(%s,%s) VALUES(%d, %d)", 20 | povit.Table().Name, 21 | povit.Source.Column.Name, 22 | povit.Target.Column.Name, 23 | povit.Source.Value, 24 | povit.Target.Value, 25 | ) 26 | } 27 | 28 | func insertFields(fields []*data.Field) string { 29 | names := make([]string, len(fields)) 30 | for i := range fields { 31 | names[i] = fields[i].Column.Name 32 | } 33 | return strings.Join(names, ",") 34 | } 35 | 36 | func insertValueSymbols(fields []*data.Field) string { 37 | array := make([]string, len(fields)) 38 | for i := range array { 39 | array[i] = "?" 40 | } 41 | return strings.Join(array, ",") 42 | } 43 | -------------------------------------------------------------------------------- /db/dialect/mysql/query_test.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "testing" 5 | 6 | "rxdrag.com/entify/model" 7 | "rxdrag.com/entify/model/table" 8 | ) 9 | 10 | func TestModifyTableName(t *testing.T) { 11 | var mysqlBuilder MySQLBuilder 12 | 13 | atoms := mysqlBuilder.BuildModifyTableAtoms( 14 | &model.TableDiff{ 15 | OldTable: &table.Table{ 16 | Name: "User", 17 | }, 18 | NewTable: &table.Table{ 19 | Name: "User2", 20 | }, 21 | }, 22 | ) 23 | 24 | if len(atoms) != 1 { 25 | t.Error("Modify atoms number error") 26 | } 27 | 28 | if atoms[0].ExcuteSQL != "ALTER TABLE User RENAME TO User2 " { 29 | t.Error("Modify atom ExcuteSQL error:#" + atoms[0].ExcuteSQL + "#") 30 | } 31 | 32 | if atoms[0].UndoSQL != "ALTER TABLE User2 RENAME TO User " { 33 | t.Error("Modify atom UndoSQL error:#" + atoms[0].UndoSQL + "#") 34 | } 35 | } 36 | 37 | func TestModifyColumnName(t *testing.T) { 38 | var mysqlBuilder MySQLBuilder 39 | atoms := mysqlBuilder.BuildModifyTableAtoms( 40 | &model.TableDiff{ 41 | OldTable: &table.Table{ 42 | Uuid: "uuid1", 43 | Name: "User", 44 | }, 45 | NewTable: &table.Table{ 46 | Uuid: "uuid1", 47 | Name: "User", 48 | }, 49 | }, 50 | ) 51 | 52 | if len(atoms) != 1 { 53 | t.Errorf("Modify atoms number error, number:%d", len(atoms)) 54 | } 55 | 56 | if atoms[0].ExcuteSQL != "ALTER TABLE User CHANGE COLUMN newColumn1 nickname text" { 57 | t.Errorf("ExcuteSQL error:" + atoms[0].ExcuteSQL) 58 | } 59 | 60 | if atoms[0].UndoSQL != "ALTER TABLE User CHANGE COLUMN nickname newColumn1 text" { 61 | t.Errorf("UndoSQL error:" + atoms[0].UndoSQL) 62 | } 63 | t.Log("#" + atoms[0].ExcuteSQL + "#") 64 | t.Log(atoms[0].UndoSQL) 65 | } 66 | -------------------------------------------------------------------------------- /db/dialect/mysql/update.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "rxdrag.com/entify/model/data" 8 | "rxdrag.com/entify/model/table" 9 | ) 10 | 11 | func (b *MySQLBuilder) BuildUpdateSQL(id uint64, fields []*data.Field, table *table.Table) string { 12 | sql := fmt.Sprintf( 13 | "UPDATE `%s` SET %s WHERE ID = %d", 14 | table.Name, 15 | updateSetFields(fields), 16 | id, 17 | ) 18 | 19 | return sql 20 | } 21 | 22 | func updateSetFields(fields []*data.Field) string { 23 | if len(fields) == 0 { 24 | panic("No update fields") 25 | } 26 | newKeys := make([]string, len(fields)) 27 | for i, field := range fields { 28 | newKeys[i] = field.Column.Name + "=?" 29 | } 30 | return strings.Join(newKeys, ",") 31 | } 32 | -------------------------------------------------------------------------------- /db/nulluint64.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | ) 7 | 8 | var ( 9 | _ sql.Scanner = (*NullUint64)(nil) 10 | _ driver.Value = (*NullUint64)(nil) 11 | ) 12 | 13 | // NullUint64 represents an uint64 that may be null. 14 | // NullUint64 implements the Scanner interface so 15 | // it can be used as a scan destination, similar to NullString. 16 | type NullUint64 struct { 17 | Uint64 uint64 18 | Valid bool // Valid is true if Uint64 is not NULL 19 | i struct { 20 | sql.NullInt64 21 | } 22 | } 23 | 24 | // Scan implements the Scanner interface. 25 | func (n *NullUint64) Scan(value interface{}) error { 26 | if err := n.i.NullInt64.Scan(value); err != nil { 27 | return err 28 | } 29 | n.Uint64 = uint64(n.i.NullInt64.Int64) 30 | n.Valid = n.i.NullInt64.Valid 31 | return nil 32 | } 33 | 34 | // Value implements the driver Valuer interface. 35 | func (n NullUint64) Value() (driver.Value, error) { 36 | if !n.Valid { 37 | return nil, nil 38 | } 39 | return int64(n.Uint64), nil 40 | } 41 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module rxdrag.com/entify 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/go-sql-driver/mysql v1.6.0 8 | github.com/google/uuid v1.3.0 9 | github.com/gorilla/websocket v1.5.0 10 | github.com/graph-gophers/dataloader v5.0.0+incompatible 11 | github.com/graphql-go/graphql v0.8.0 12 | github.com/mitchellh/mapstructure v1.4.3 13 | github.com/spf13/viper v1.11.0 14 | golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 15 | ) 16 | 17 | require ( 18 | github.com/fsnotify/fsnotify v1.5.1 // indirect 19 | github.com/hashicorp/hcl v1.0.0 // indirect 20 | github.com/magiconair/properties v1.8.6 // indirect 21 | github.com/opentracing/opentracing-go v1.2.0 // indirect 22 | github.com/pelletier/go-toml v1.9.4 // indirect 23 | github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect 24 | github.com/spf13/afero v1.8.2 // indirect 25 | github.com/spf13/cast v1.4.1 // indirect 26 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | github.com/subosito/gotenv v1.2.0 // indirect 29 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect 30 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect 31 | golang.org/x/text v0.3.7 // indirect 32 | gopkg.in/ini.v1 v1.66.4 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /handler/README.md: -------------------------------------------------------------------------------- 1 | # graphql-go-handler 2 | 3 | Fork of [https://github.com/bhoriuchi/graphql-go-tools](https://github.com/bhoriuchi/graphql-go-tools) with some changes 4 | 5 | ### Usage 6 | 7 | ```go 8 | func main() { 9 | schema, _ := graphql.NewSchema(...) 10 | 11 | h := handler.New(&handler.Config{ 12 | Schema: &schema, 13 | Pretty: true, 14 | GraphiQL: handler.NewDefaultGraphiQLConfig(), 15 | }) 16 | 17 | http.Handle("/graphql", h) 18 | http.ListenAndServe(":8080", nil) 19 | } 20 | ``` 21 | 22 | ### Using Playground 23 | ```go 24 | h := handler.New(&handler.Config{ 25 | Schema: &schema, 26 | Pretty: true, 27 | Playground: handler.NewDefaultPlaygroundConfig(),, 28 | }) 29 | ``` 30 | 31 | ### Details 32 | 33 | The handler will accept requests with 34 | the parameters: 35 | 36 | * **`query`**: A string GraphQL document to be executed. 37 | 38 | * **`variables`**: The runtime values to use for any GraphQL query variables 39 | as a JSON object. 40 | 41 | * **`operationName`**: If the provided `query` contains multiple named 42 | operations, this specifies which operation should be executed. If not 43 | provided, an 400 error will be returned if the `query` contains multiple 44 | named operations. 45 | 46 | GraphQL will first look for each parameter in the URL's query-string: 47 | 48 | ``` 49 | /graphql?query=query+getUser($id:ID){user(id:$id){name}}&variables={"id":"4"} 50 | ``` 51 | 52 | If not found in the query-string, it will look in the POST request body. 53 | The `handler` will interpret it 54 | depending on the provided `Content-Type` header. 55 | 56 | * **`application/json`**: the POST body will be parsed as a JSON 57 | object of parameters. 58 | 59 | * **`application/x-www-form-urlencoded`**: this POST body will be 60 | parsed as a url-encoded string of key-value pairs. 61 | 62 | * **`application/graphql`**: The POST body will be parsed as GraphQL 63 | query string, which provides the `query` parameter. 64 | -------------------------------------------------------------------------------- /handler/handlerfunc.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | "sync" 9 | 10 | "github.com/google/uuid" 11 | "github.com/gorilla/websocket" 12 | "github.com/graphql-go/graphql" 13 | ) 14 | 15 | var upgrader = websocket.Upgrader{ 16 | ReadBufferSize: 1024, 17 | WriteBufferSize: 1024, 18 | CheckOrigin: func(r *http.Request) bool { 19 | return true 20 | }, 21 | Subprotocols: []string{"graphql-ws"}, 22 | } 23 | 24 | type ConnectionACKMessage struct { 25 | OperationID string `json:"id,omitempty"` 26 | Type string `json:"type"` 27 | Payload struct { 28 | Query string `json:"query"` 29 | } `json:"payload,omitempty"` 30 | } 31 | 32 | func NewFunc(schemaResolveFn SchemaResolveFunc) func(w http.ResponseWriter, r *http.Request) { 33 | return func(w http.ResponseWriter, r *http.Request) { 34 | conn, err := upgrader.Upgrade(w, r, nil) 35 | if err != nil { 36 | log.Printf("failed to do websocket upgrade: %v", err) 37 | w.WriteHeader(http.StatusInternalServerError) 38 | return 39 | } 40 | 41 | connectionACK, err := json.Marshal(map[string]string{ 42 | "type": "connection_ack", 43 | }) 44 | if err != nil { 45 | log.Printf("failed to marshal ws connection ack: %v", err) 46 | w.WriteHeader(http.StatusInternalServerError) 47 | return 48 | } 49 | 50 | if err := conn.WriteMessage(websocket.TextMessage, connectionACK); err != nil { 51 | log.Printf("failed to write to ws connection: %v", err) 52 | w.WriteHeader(http.StatusInternalServerError) 53 | return 54 | } 55 | go handleSubscription(conn, schemaResolveFn) 56 | } 57 | } 58 | 59 | func handleSubscription(conn *websocket.Conn, schemaResolveFn SchemaResolveFunc) { 60 | var subscriber *Subscriber 61 | subscriptionCtx, subscriptionCancelFn := context.WithCancel(context.Background()) 62 | 63 | handleClosedConnection := func() { 64 | log.Println("[SubscriptionsHandler] subscriber closed connection") 65 | unsubscribe(subscriptionCancelFn, subscriber) 66 | return 67 | } 68 | 69 | for { 70 | _, p, err := conn.ReadMessage() 71 | if err != nil { 72 | log.Printf("failed to read websocket message: %v", err) 73 | return 74 | } 75 | 76 | var msg ConnectionACKMessage 77 | if err := json.Unmarshal(p, &msg); err != nil { 78 | log.Printf("failed to unmarshal websocket message: %v", err) 79 | continue 80 | } 81 | 82 | if msg.Type == "stop" { 83 | handleClosedConnection() 84 | return 85 | } 86 | 87 | if msg.Type == "start" { 88 | subscriber = subscribe(subscriptionCtx, subscriptionCancelFn, conn, msg, schemaResolveFn) 89 | } 90 | } 91 | } 92 | 93 | type Subscriber struct { 94 | UUID string 95 | Conn *websocket.Conn 96 | RequestString string 97 | OperationID string 98 | } 99 | 100 | var subscribers sync.Map 101 | 102 | func subscribersSize() uint64 { 103 | var size uint64 104 | subscribers.Range(func(_, _ interface{}) bool { 105 | size++ 106 | return true 107 | }) 108 | return size 109 | } 110 | 111 | func unsubscribe(subscriptionCancelFn context.CancelFunc, subscriber *Subscriber) { 112 | subscriptionCancelFn() 113 | if subscriber != nil { 114 | subscriber.Conn.Close() 115 | subscribers.Delete(subscriber.UUID) 116 | } 117 | log.Printf("[SubscriptionsHandler] subscribers size: %+v", subscribersSize()) 118 | } 119 | 120 | func subscribe(ctx context.Context, 121 | subscriptionCancelFn context.CancelFunc, 122 | conn *websocket.Conn, msg ConnectionACKMessage, 123 | schemaResolveFn SchemaResolveFunc, 124 | ) *Subscriber { 125 | subscriber := &Subscriber{ 126 | UUID: uuid.New().String(), 127 | Conn: conn, 128 | RequestString: msg.Payload.Query, 129 | OperationID: msg.OperationID, 130 | } 131 | subscribers.Store(subscriber.UUID, &subscriber) 132 | 133 | log.Printf("[SubscriptionsHandler] subscribers size: %+v", subscribersSize()) 134 | 135 | sendMessage := func(r *graphql.Result) error { 136 | message, err := json.Marshal(map[string]interface{}{ 137 | "type": "data", 138 | "id": subscriber.OperationID, 139 | "payload": r.Data, 140 | }) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | if err := subscriber.Conn.WriteMessage(websocket.TextMessage, message); err != nil { 146 | return err 147 | } 148 | 149 | return nil 150 | } 151 | 152 | go func() { 153 | subscribeParams := graphql.Params{ 154 | Context: ctx, 155 | RequestString: msg.Payload.Query, 156 | Schema: *schemaResolveFn(), 157 | } 158 | 159 | subscribeChannel := graphql.Subscribe(subscribeParams) 160 | 161 | for { 162 | select { 163 | case <-ctx.Done(): 164 | log.Printf("[SubscriptionsHandler] subscription ctx done") 165 | return 166 | case r, isOpen := <-subscribeChannel: 167 | if !isOpen { 168 | log.Printf("[SubscriptionsHandler] subscription channel closed") 169 | unsubscribe(subscriptionCancelFn, subscriber) 170 | return 171 | } 172 | if err := sendMessage(r); err != nil { 173 | if err == websocket.ErrCloseSent { 174 | unsubscribe(subscriptionCancelFn, subscriber) 175 | } 176 | log.Printf("failed to send message: %v", err) 177 | } 178 | } 179 | } 180 | }() 181 | 182 | return subscriber 183 | } 184 | -------------------------------------------------------------------------------- /handler/playground.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | "net/url" 8 | "path" 9 | ) 10 | 11 | // PlaygroundConfig playground configuration 12 | type PlaygroundConfig struct { 13 | Endpoint string 14 | SubscriptionEndpoint string 15 | Version string 16 | } 17 | 18 | // NewDefaultPlaygroundConfig creates a new default config 19 | func NewDefaultPlaygroundConfig() *PlaygroundConfig { 20 | return &PlaygroundConfig{ 21 | Endpoint: "", 22 | SubscriptionEndpoint: "", 23 | Version: PlaygroundVersion, 24 | } 25 | } 26 | 27 | type playgroundData struct { 28 | PlaygroundVersion string 29 | Endpoint string 30 | SubscriptionEndpoint string 31 | SetTitle bool 32 | } 33 | 34 | // renderPlayground renders the Playground GUI 35 | func renderPlayground(config *PlaygroundConfig, w http.ResponseWriter, r *http.Request) { 36 | t := template.New("Playground") 37 | t, err := t.Parse(graphcoolPlaygroundTemplate) 38 | if err != nil { 39 | http.Error(w, err.Error(), http.StatusInternalServerError) 40 | return 41 | } 42 | 43 | endpoint := r.URL.Path 44 | if config.Endpoint != "" { 45 | endpoint = config.Endpoint 46 | } 47 | 48 | subscriptionPath := path.Join(path.Dir(r.URL.Path), "subscriptions") 49 | subscriptionEndpoint := fmt.Sprintf("ws://%v%s", r.Host, subscriptionPath) 50 | if config.SubscriptionEndpoint != "" { 51 | if _, err := url.ParseRequestURI(config.SubscriptionEndpoint); err == nil { 52 | subscriptionEndpoint = config.SubscriptionEndpoint 53 | } else { 54 | subscriptionEndpoint = path.Join( 55 | fmt.Sprintf("ws://%v", r.Host), 56 | config.SubscriptionEndpoint, 57 | ) 58 | } 59 | } 60 | 61 | version := PlaygroundVersion 62 | if config.Version != "" { 63 | version = config.Version 64 | } 65 | 66 | d := playgroundData{ 67 | PlaygroundVersion: version, 68 | Endpoint: endpoint, 69 | SubscriptionEndpoint: subscriptionEndpoint, 70 | SetTitle: true, 71 | } 72 | err = t.ExecuteTemplate(w, "index", d) 73 | if err != nil { 74 | http.Error(w, err.Error(), http.StatusInternalServerError) 75 | } 76 | 77 | return 78 | } 79 | 80 | // PlaygroundVersion the default version to use 81 | var PlaygroundVersion = "1.7.20" 82 | 83 | const graphcoolPlaygroundTemplate = ` 84 | {{ define "index" }} 85 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | GraphQL Playground 100 | 101 | 102 | 103 | 104 | 105 | 106 |
107 | 134 | 135 |
Loading 136 | GraphQL Playground 137 |
138 |
139 | 147 | 148 | 149 | 150 | {{ end }} 151 | ` 152 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | "rxdrag.com/entify/authentication" 9 | "rxdrag.com/entify/config" 10 | "rxdrag.com/entify/consts" 11 | "rxdrag.com/entify/db" 12 | "rxdrag.com/entify/handler" 13 | "rxdrag.com/entify/repository" 14 | "rxdrag.com/entify/resolve" 15 | "rxdrag.com/entify/schema" 16 | ) 17 | 18 | const PORT = 4000 19 | 20 | func checkParams() { 21 | dbConfig := config.GetDbConfig() 22 | if dbConfig.Driver == "" || 23 | dbConfig.Host == "" || 24 | dbConfig.Database == "" || 25 | dbConfig.User == "" || 26 | dbConfig.Port == "" || 27 | dbConfig.Password == "" { 28 | panic("Params is not enough, please set") 29 | } 30 | } 31 | 32 | func checkMetaInstall() { 33 | if !repository.IsEntityExists(consts.META_ENTITY_NAME) { 34 | repository.Install() 35 | } 36 | } 37 | 38 | // func checkMediaInstall() { 39 | // if !repository.IsEntityExists(consts.MEDIA_ENTITY_NAME) { 40 | // resolve.InstallMedia() 41 | // } 42 | // } 43 | 44 | func main() { 45 | defer db.Close() 46 | checkParams() 47 | checkMetaInstall() 48 | repository.InitGlobalModel() 49 | repository.LoadModel() 50 | // if config.Storage() != "" { 51 | // checkMediaInstall() 52 | // } 53 | if config.AuthUrl() == "" && !repository.IsEntityExists(consts.META_USER) { 54 | schema.InitAuthInstallSchema() 55 | } else { 56 | schema.InitSchema() 57 | } 58 | 59 | h := handler.New(&handler.Config{ 60 | SchemaResolveFn: schema.ResolveSchema, 61 | Pretty: true, 62 | GraphiQLConfig: &handler.GraphiQLConfig{}, 63 | }) 64 | 65 | http.Handle("/graphql", 66 | authentication.CorsMiddleware( 67 | authentication.AuthMiddleware( 68 | resolve.LoadersMiddleware(h), 69 | ), 70 | ), 71 | ) 72 | http.HandleFunc("/subscriptions", handler.NewFunc(schema.ResolveSchema)) 73 | if config.Storage() == consts.LOCAL { 74 | fmt.Println(fmt.Sprintf("Running a file server at http://localhost:%d/uploads/", PORT)) 75 | http.Handle(consts.UPLOAD_PRIFIX+"/", http.StripPrefix(consts.UPLOAD_PRIFIX, http.FileServer(http.Dir(consts.UPLOAD_PATH)))) 76 | } 77 | 78 | fmt.Println(fmt.Sprintf("Running a GraphQL API server at http://localhost:%d/graphql", PORT)) 79 | fmt.Println(fmt.Sprintf("Subscriptions endpoint is http://localhost:%d/subscriptions", PORT)) 80 | err2 := http.ListenAndServe(fmt.Sprintf(":%d", PORT), nil) 81 | if err2 != nil { 82 | fmt.Printf("启动失败:%s", err2) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /model/data/instance.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "rxdrag.com/entify/consts" 9 | "rxdrag.com/entify/model/graph" 10 | "rxdrag.com/entify/model/table" 11 | ) 12 | 13 | type Field struct { 14 | Column *table.Column 15 | Value interface{} 16 | } 17 | 18 | type Instance struct { 19 | Id uint64 20 | Entity *graph.Entity 21 | Fields []*Field 22 | Associations []Associationer 23 | } 24 | 25 | func NewInstance(object map[string]interface{}, entity *graph.Entity) *Instance { 26 | instance := Instance{ 27 | Entity: entity, 28 | } 29 | if object[consts.ID] != nil { 30 | instance.Id = parseId(object[consts.ID]) 31 | } 32 | 33 | columns := entity.Table.Columns 34 | for i := range columns { 35 | column := columns[i] 36 | if object[column.Name] != nil { 37 | instance.Fields = append(instance.Fields, &Field{ 38 | Column: column, 39 | Value: object[column.Name], 40 | }) 41 | } else if column.CreateDate || column.UpdateDate { 42 | instance.Fields = append(instance.Fields, &Field{ 43 | Column: column, 44 | Value: time.Now(), 45 | }) 46 | } 47 | } 48 | allAssociation := entity.AllAssociations() 49 | for i := range allAssociation { 50 | asso := allAssociation[i] 51 | if !asso.IsAbstract() { 52 | value := object[asso.Name()] 53 | if value != nil { 54 | ref := Reference{ 55 | Association: asso, 56 | Value: value.(map[string]interface{}), 57 | } 58 | instance.Associations = append(instance.Associations, &ref) 59 | } 60 | 61 | } else { 62 | derivedAssociations := asso.DerivedAssociationsByOwnerUuid(entity.Uuid()) 63 | for j := range derivedAssociations { 64 | derivedAsso := derivedAssociations[j] 65 | value := object[derivedAsso.Name()] 66 | if value != nil { 67 | ref := DerivedReference{ 68 | Association: derivedAsso, 69 | Value: value.(map[string]interface{}), 70 | } 71 | instance.Associations = append(instance.Associations, &ref) 72 | } 73 | } 74 | } 75 | } 76 | return &instance 77 | } 78 | 79 | func (ins *Instance) IsInsert() bool { 80 | for i := range ins.Fields { 81 | field := ins.Fields[i] 82 | if field.Column.Name == consts.ID { 83 | if field.Value != nil { 84 | return false 85 | } 86 | } 87 | } 88 | return true 89 | } 90 | 91 | func (ins *Instance) Table() *table.Table { 92 | return ins.Entity.Table 93 | } 94 | 95 | func parseId(id interface{}) uint64 { 96 | switch v := id.(type) { 97 | default: 98 | panic(fmt.Sprintf("unexpected id type %T", v)) 99 | case uint64: 100 | return id.(uint64) 101 | case string: 102 | u, err := strconv.ParseUint(id.(string), 0, 64) 103 | if err != nil { 104 | panic(err.Error()) 105 | } 106 | return u 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /model/data/povit.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "rxdrag.com/entify/model/table" 4 | 5 | type AssociationPovit struct { 6 | Source *Field 7 | Target *Field 8 | //Fields []*Field 9 | Association Associationer 10 | } 11 | 12 | type DerivedAssociationPovit struct { 13 | Source *Field 14 | Target *Field 15 | //Fields []*Field 16 | DerivedReference *DerivedReference 17 | } 18 | 19 | func NewAssociationPovit(association Associationer, sourceId uint64, targetId uint64) *AssociationPovit { 20 | sourceColumn := association.SourceColumn() 21 | targetColumn := association.TargetColumn() 22 | povit := AssociationPovit{ 23 | Association: association, 24 | Source: &Field{ 25 | Column: sourceColumn, 26 | Value: sourceId, 27 | }, 28 | Target: &Field{ 29 | Column: targetColumn, 30 | Value: targetId, 31 | }, 32 | } 33 | 34 | return &povit 35 | } 36 | 37 | func (a *AssociationPovit) Table() *table.Table { 38 | return a.Association.Table() 39 | } 40 | 41 | // func NewDerivedAssociationPovit(association Associationer, sourceId uint64, targetId uint64) *AssociationPovit { 42 | // sourceColumn := association.SourceColumn() 43 | // targetColumn := association.TargetColumn() 44 | // povit := DerivedAssociationPovit{ 45 | // Association: association, 46 | // source: &Field{ 47 | // Column: sourceColumn, 48 | // Value: sourceId, 49 | // }, 50 | // target: &Field{ 51 | // Column: targetColumn, 52 | // Value: targetId, 53 | // }, 54 | // } 55 | 56 | // return &povit 57 | // } 58 | -------------------------------------------------------------------------------- /model/data/relation.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "rxdrag.com/entify/consts" 5 | "rxdrag.com/entify/model/graph" 6 | "rxdrag.com/entify/model/meta" 7 | "rxdrag.com/entify/model/table" 8 | ) 9 | 10 | // type HasOne struct { 11 | // Cascade bool 12 | // Add Instance 13 | // Delete Instance 14 | // Update Instance 15 | // Sync Instance 16 | // } 17 | 18 | // type HasMany struct { 19 | // Cascade bool 20 | // Add []Instance 21 | // Delete []Instance 22 | // Update []Instance 23 | // Sync []Instance 24 | // } 25 | 26 | type Associationer interface { 27 | Deleted() []*Instance 28 | Added() []*Instance 29 | Updated() []*Instance 30 | Synced() []*Instance 31 | Cascade() bool 32 | SourceColumn() *table.Column 33 | TargetColumn() *table.Column 34 | Table() *table.Table 35 | IsSource() bool 36 | OwnerColumn() *table.Column 37 | TypeColumn() *table.Column 38 | //Entity, Partial and External 39 | TypeEntity() *graph.Entity 40 | IsCombination() bool 41 | } 42 | 43 | //没有继承关系的关联 44 | type Reference struct { 45 | Association *graph.Association 46 | Value map[string]interface{} 47 | } 48 | 49 | type DerivedReference struct { 50 | Association *graph.DerivedAssociation 51 | Value map[string]interface{} 52 | } 53 | 54 | func convertToInstances(objects interface{}, entity *graph.Entity) []*Instance { 55 | instances := []*Instance{} 56 | if objects != nil { 57 | objs := objects.([]interface{}) 58 | for i := range objs { 59 | instances = append(instances, NewInstance(objs[i].(map[string]interface{}), entity)) 60 | } 61 | } 62 | return instances 63 | } 64 | 65 | func (r *Reference) Deleted() []*Instance { 66 | return convertToInstances(r.Value[consts.ARG_DELETE], r.TypeEntity()) 67 | } 68 | 69 | func (r *Reference) Added() []*Instance { 70 | return convertToInstances(r.Value[consts.ARG_ADD], r.TypeEntity()) 71 | } 72 | 73 | func (r *Reference) Updated() []*Instance { 74 | return convertToInstances(r.Value[consts.ARG_UPDATE], r.TypeEntity()) 75 | } 76 | 77 | func (r *Reference) Synced() []*Instance { 78 | return convertToInstances(r.Value[consts.ARG_SYNC], r.TypeEntity()) 79 | } 80 | 81 | func (r *Reference) Cascade() bool { 82 | if r.Value[consts.ARG_CASCADE] != nil { 83 | return r.Value[consts.ARG_CASCADE].(bool) 84 | } 85 | return false 86 | } 87 | 88 | func (r *Reference) SourceColumn() *table.Column { 89 | for i := range r.Association.Relation.Table.Columns { 90 | column := r.Association.Relation.Table.Columns[i] 91 | if column.Name == r.Association.Relation.SourceClass().TableName() { 92 | return column 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func (r *Reference) TargetColumn() *table.Column { 99 | for i := range r.Association.Relation.Table.Columns { 100 | column := r.Association.Relation.Table.Columns[i] 101 | if column.Name == r.Association.Relation.TargetClass().TableName() { 102 | return column 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | func (r *Reference) Table() *table.Table { 109 | return r.Association.Relation.Table 110 | } 111 | 112 | func (r *Reference) IsSource() bool { 113 | return r.Association.IsSource() 114 | } 115 | 116 | func (r *Reference) OwnerColumn() *table.Column { 117 | if r.IsSource() { 118 | return r.SourceColumn() 119 | } else { 120 | return r.TargetColumn() 121 | } 122 | } 123 | func (r *Reference) TypeColumn() *table.Column { 124 | if !r.IsSource() { 125 | return r.SourceColumn() 126 | } else { 127 | return r.TargetColumn() 128 | } 129 | } 130 | 131 | func (r *Reference) TypeEntity() *graph.Entity { 132 | entity := r.Association.TypeEntity() 133 | if entity != nil { 134 | return entity 135 | } 136 | 137 | partial := r.Association.TypePartial() 138 | 139 | if partial != nil { 140 | return &partial.Entity 141 | } 142 | 143 | external := r.Association.TypeExternal() 144 | 145 | if external != nil { 146 | return &external.Entity 147 | } 148 | panic("Can not find reference entity") 149 | } 150 | 151 | func (r *Reference) IsCombination() bool { 152 | return r.IsSource() && 153 | (r.Association.Relation.RelationType == meta.TWO_WAY_COMBINATION || 154 | r.Association.Relation.RelationType == meta.ONE_WAY_COMBINATION) 155 | } 156 | 157 | //====derived 158 | func (r *DerivedReference) Deleted() []*Instance { 159 | return convertToInstances(r.Value[consts.ARG_DELETE], r.TypeEntity()) 160 | } 161 | 162 | func (r *DerivedReference) Added() []*Instance { 163 | return convertToInstances(r.Value[consts.ARG_ADD], r.TypeEntity()) 164 | } 165 | 166 | func (r *DerivedReference) Updated() []*Instance { 167 | return convertToInstances(r.Value[consts.ARG_UPDATE], r.TypeEntity()) 168 | } 169 | 170 | func (r *DerivedReference) Synced() []*Instance { 171 | return convertToInstances(r.Value[consts.ARG_SYNC], r.TypeEntity()) 172 | } 173 | 174 | func (r *DerivedReference) Cascade() bool { 175 | return r.Value[consts.ARG_CASCADE].(bool) 176 | } 177 | 178 | func (r *DerivedReference) SourceColumn() *table.Column { 179 | for i := range r.Association.Relation.Table.Columns { 180 | column := r.Association.Relation.Table.Columns[i] 181 | if column.Name == r.Association.Relation.SourceClass().TableName() { 182 | return column 183 | } 184 | } 185 | return nil 186 | } 187 | 188 | func (r *DerivedReference) TargetColumn() *table.Column { 189 | for i := range r.Association.Relation.Table.Columns { 190 | column := r.Association.Relation.Table.Columns[i] 191 | if column.Name == r.Association.Relation.TargetClass().TableName() { 192 | return column 193 | } 194 | } 195 | return nil 196 | } 197 | 198 | func (r *DerivedReference) Table() *table.Table { 199 | return r.Association.Relation.Table 200 | } 201 | 202 | func (r *DerivedReference) IsSource() bool { 203 | return r.Association.DerivedFrom.IsSource() 204 | } 205 | 206 | func (r *DerivedReference) OwnerColumn() *table.Column { 207 | if r.IsSource() { 208 | return r.SourceColumn() 209 | } else { 210 | return r.TargetColumn() 211 | } 212 | } 213 | func (r *DerivedReference) TypeColumn() *table.Column { 214 | if !r.IsSource() { 215 | return r.SourceColumn() 216 | } else { 217 | return r.TargetColumn() 218 | } 219 | } 220 | 221 | func (r *DerivedReference) TypeEntity() *graph.Entity { 222 | entity := r.Association.TypeEntity() 223 | if entity != nil { 224 | return entity 225 | } 226 | 227 | partial := r.Association.TypePartial() 228 | 229 | if partial != nil { 230 | return &partial.Entity 231 | } 232 | 233 | external := r.Association.TypeExternal() 234 | 235 | if external != nil { 236 | return &external.Entity 237 | } 238 | panic("Can not find reference entity") 239 | } 240 | 241 | func (r *DerivedReference) IsCombination() bool { 242 | return r.IsSource() && 243 | (r.Association.Relation.Parent.RelationType == meta.TWO_WAY_COMBINATION || 244 | r.Association.Relation.Parent.RelationType == meta.ONE_WAY_COMBINATION) 245 | } 246 | -------------------------------------------------------------------------------- /model/diff.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "rxdrag.com/entify/model/table" 4 | 5 | type ModifyAtom struct { 6 | ExcuteSQL string 7 | UndoSQL string 8 | } 9 | 10 | type ColumnDiff struct { 11 | OldColumn *table.Column 12 | NewColumn *table.Column 13 | } 14 | 15 | type TableDiff struct { 16 | OldTable *table.Table 17 | NewTable *table.Table 18 | DeleteColumns []*table.Column 19 | AddColumns []*table.Column 20 | ModifyColumns []ColumnDiff //删除列索引,并重建 21 | } 22 | 23 | type Diff struct { 24 | oldContent *Model 25 | newContent *Model 26 | 27 | DeletedTables []*table.Table 28 | AddedTables []*table.Table 29 | ModifiedTables []*TableDiff 30 | } 31 | 32 | func findTable(uuid string, tables []*table.Table) *table.Table { 33 | for i := range tables { 34 | if tables[i].Uuid == uuid { 35 | return tables[i] 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | func findColumn(uuid string, columns []*table.Column) *table.Column { 42 | for _, column := range columns { 43 | if column.Uuid == uuid { 44 | return column 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func columnDifferent(oldColumn, newColumn *table.Column) *ColumnDiff { 52 | diff := ColumnDiff{ 53 | OldColumn: oldColumn, 54 | NewColumn: newColumn, 55 | } 56 | if oldColumn.Name != newColumn.Name { 57 | return &diff 58 | } 59 | // if oldColumn.Generated != newColumn.Generated { 60 | // return &diff 61 | // } 62 | if oldColumn.Index != newColumn.Index { 63 | return &diff 64 | } 65 | if oldColumn.Nullable != newColumn.Nullable { 66 | return &diff 67 | } 68 | if oldColumn.Length != newColumn.Length { 69 | return &diff 70 | } 71 | if oldColumn.Primary != newColumn.Primary { 72 | return &diff 73 | } 74 | 75 | if oldColumn.Unique != newColumn.Unique { 76 | return &diff 77 | } 78 | 79 | if oldColumn.Type != newColumn.Type { 80 | return &diff 81 | } 82 | return nil 83 | } 84 | func tableDifferent(oldTable, newTable *table.Table) *TableDiff { 85 | var diff TableDiff 86 | modified := false 87 | diff.OldTable = oldTable 88 | diff.NewTable = newTable 89 | 90 | for _, column := range oldTable.Columns { 91 | foundCoumn := findColumn(column.Uuid, newTable.Columns) 92 | if foundCoumn == nil { 93 | diff.DeleteColumns = append(diff.DeleteColumns, column) 94 | modified = true 95 | } 96 | } 97 | 98 | for _, column := range newTable.Columns { 99 | foundColumn := findColumn(column.Uuid, oldTable.Columns) 100 | if foundColumn == nil { 101 | diff.AddColumns = append(diff.AddColumns, column) 102 | modified = true 103 | } else { 104 | columnDiff := columnDifferent(foundColumn, column) 105 | if columnDiff != nil { 106 | diff.ModifyColumns = append(diff.ModifyColumns, *columnDiff) 107 | modified = true 108 | } 109 | } 110 | } 111 | 112 | if diff.OldTable.Name != diff.NewTable.Name || modified { 113 | return &diff 114 | } 115 | return nil 116 | } 117 | 118 | func CreateDiff(published, next *Model) *Diff { 119 | diff := Diff{ 120 | oldContent: published, 121 | newContent: next, 122 | } 123 | 124 | publishedTables := published.Graph.Tables 125 | nextTables := next.Graph.Tables 126 | 127 | for _, table := range publishedTables { 128 | foundTable := findTable(table.Uuid, nextTables) 129 | //删除的Table 130 | if foundTable == nil { 131 | diff.DeletedTables = append(diff.DeletedTables, table) 132 | } 133 | } 134 | for _, table := range nextTables { 135 | foundTable := findTable(table.Uuid, publishedTables) 136 | //添加的Table 137 | if foundTable == nil { 138 | diff.AddedTables = append(diff.AddedTables, table) 139 | } else { //修改的Table 140 | tableDiff := tableDifferent(foundTable, table) 141 | if tableDiff != nil { 142 | diff.ModifiedTables = append(diff.ModifiedTables, tableDiff) 143 | } 144 | } 145 | } 146 | 147 | return &diff 148 | } 149 | -------------------------------------------------------------------------------- /model/domain/attribute.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Attribute struct { 6 | meta.AttributeMeta 7 | Class *Class 8 | } 9 | 10 | func NewAttribute(a *meta.AttributeMeta, c *Class) *Attribute { 11 | return &Attribute{ 12 | AttributeMeta: *a, 13 | Class: c, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /model/domain/class.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Class struct { 6 | Uuid string 7 | InnerId uint64 8 | StereoType string 9 | Name string 10 | PartialName string 11 | Description string 12 | Root bool 13 | SoftDelete bool 14 | Attributes []*Attribute 15 | Methods []*Method 16 | parents []*Class 17 | Children []*Class 18 | } 19 | 20 | func NewClass(c *meta.ClassMeta) *Class { 21 | cls := Class{ 22 | Uuid: c.Uuid, 23 | InnerId: c.InnerId, 24 | StereoType: c.StereoType, 25 | Name: c.Name, 26 | PartialName: c.PartialName, 27 | Description: c.Description, 28 | Root: c.Root, 29 | SoftDelete: c.SoftDelete, 30 | Attributes: make([]*Attribute, len(c.Attributes)), 31 | Methods: make([]*Method, len(c.Methods)), 32 | parents: []*Class{}, 33 | Children: []*Class{}, 34 | } 35 | 36 | for i := range c.Attributes { 37 | cls.Attributes[i] = NewAttribute(&c.Attributes[i], &cls) 38 | } 39 | 40 | for i := range c.Methods { 41 | cls.Methods[i] = NewMethod(&c.Methods[i], &cls) 42 | } 43 | 44 | return &cls 45 | } 46 | 47 | func (c *Class) HasChildren() bool { 48 | return len(c.Children) > 0 49 | } 50 | 51 | func (c *Class) AllParents() []*Class { 52 | parents := []*Class{} 53 | for i := range c.parents { 54 | parent := c.parents[i] 55 | parents = append(parents, parent) 56 | parents = append(parents, parent.AllParents()...) 57 | } 58 | 59 | return parents 60 | } 61 | -------------------------------------------------------------------------------- /model/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | /** 4 | * 在domain层,把有子类的实体,拆分成接口+实体 5 | * 比如A => A + AEntity 6 | */ 7 | 8 | import ( 9 | "rxdrag.com/entify/model/meta" 10 | ) 11 | 12 | type Model struct { 13 | Enums []*Enum 14 | Classes []*Class 15 | Relations []*Relation 16 | } 17 | 18 | func New(m *meta.Model) *Model { 19 | model := Model{} 20 | 21 | for i := range m.Classes { 22 | class := m.Classes[i] 23 | if class.StereoType == meta.CLASSS_ENUM { 24 | model.Enums = append(model.Enums, NewEnum(class)) 25 | } else { 26 | model.Classes = append(model.Classes, NewClass(class)) 27 | } 28 | } 29 | 30 | for i := range m.Relations { 31 | relation := m.Relations[i] 32 | 33 | src := model.GetClassByUuid(relation.SourceId) 34 | tar := model.GetClassByUuid(relation.TargetId) 35 | if src == nil || tar == nil { 36 | panic("Meta is not integral, can not find class of relation:" + relation.Uuid) 37 | } 38 | if relation.RelationType == meta.INHERIT { 39 | src.parents = append(src.parents, tar) 40 | tar.Children = append(tar.Children, src) 41 | } else { 42 | r := NewRelation(relation, src, tar) 43 | model.Relations = append(model.Relations, r) 44 | } 45 | } 46 | 47 | return &model 48 | } 49 | 50 | func (m *Model) GetClassByUuid(uuid string) *Class { 51 | for i := range m.Classes { 52 | cls := m.Classes[i] 53 | if cls.Uuid == uuid { 54 | return cls 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /model/domain/enum.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Enum struct { 6 | Uuid string 7 | Name string 8 | Values []string 9 | } 10 | 11 | func NewEnum(c *meta.ClassMeta) *Enum { 12 | enum := Enum{ 13 | Uuid: c.Uuid, 14 | Name: c.Name, 15 | Values: make([]string, len(c.Attributes)), 16 | } 17 | 18 | for i := range c.Attributes { 19 | enum.Values[i] = c.Attributes[i].Name 20 | } 21 | 22 | return &enum 23 | } 24 | -------------------------------------------------------------------------------- /model/domain/method.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Method struct { 6 | meta.MethodMeta 7 | Class *Class 8 | } 9 | 10 | func NewMethod(m *meta.MethodMeta, c *Class) *Method { 11 | return &Method{ 12 | MethodMeta: *m, 13 | Class: c, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /model/domain/relation.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Relation struct { 6 | Uuid string 7 | InnerId uint64 8 | RelationType string 9 | Source *Class 10 | Target *Class 11 | RoleOfTarget string 12 | RoleOfSource string 13 | DescriptionOnSource string 14 | DescriptionOnTarget string 15 | SourceMutiplicity string 16 | TargetMultiplicity string 17 | EnableAssociaitonClass bool 18 | AssociationClass meta.AssociationClass 19 | } 20 | 21 | func NewRelation(r *meta.RelationMeta, s *Class, t *Class) *Relation { 22 | return &Relation{ 23 | Uuid: r.Uuid, 24 | InnerId: r.InnerId, 25 | RelationType: r.RelationType, 26 | Source: s, 27 | Target: t, 28 | RoleOfTarget: r.RoleOfTarget, 29 | RoleOfSource: r.RoleOfSource, 30 | DescriptionOnSource: r.DescriptionOnSource, 31 | DescriptionOnTarget: r.DescriptionOnTarget, 32 | SourceMutiplicity: r.SourceMutiplicity, 33 | TargetMultiplicity: r.TargetMultiplicity, 34 | EnableAssociaitonClass: r.EnableAssociaitonClass, 35 | AssociationClass: r.AssociationClass, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /model/graph/args.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | const PREFIX_T string = "t" 10 | 11 | type QueryArg = map[string]interface{} 12 | 13 | type Ider interface { 14 | CreateId() int 15 | } 16 | 17 | type ArgAssociation struct { 18 | Association *Association 19 | ArgEntities []*ArgEntity 20 | } 21 | 22 | type ArgEntity struct { 23 | Id int 24 | Entity *Entity 25 | Partial *Partial 26 | Associations []*ArgAssociation 27 | ExpressionArgs map[string]interface{} 28 | } 29 | 30 | func argEntitiesFromAssociation(associ *Association, ider Ider) []*ArgEntity { 31 | var argEntities []*ArgEntity 32 | typeInterface := associ.TypeInterface() 33 | typeEntity := associ.TypeEntity() 34 | typePartial := associ.TypePartial() 35 | if typeInterface != nil { 36 | children := typeInterface.Children 37 | for i := range children { 38 | argEntities = append(argEntities, &ArgEntity{ 39 | Id: ider.CreateId(), 40 | Entity: children[i], 41 | }) 42 | } 43 | } else { 44 | argEntities = append(argEntities, &ArgEntity{ 45 | Id: ider.CreateId(), 46 | Entity: typeEntity, 47 | Partial: typePartial, 48 | }) 49 | } 50 | return argEntities 51 | } 52 | 53 | func (a *ArgEntity) GetAssociation(name string) *ArgAssociation { 54 | for i := range a.Associations { 55 | if a.Associations[i].Association.Name() == name { 56 | return a.Associations[i] 57 | } 58 | } 59 | panic("Can not find entity association:" + a.Entity.Name() + "." + name) 60 | } 61 | 62 | func (a *ArgEntity) GetWithMakeAssociation(name string, ider Ider) *ArgAssociation { 63 | for i := range a.Associations { 64 | if a.Associations[i].Association.Name() == name { 65 | return a.Associations[i] 66 | } 67 | } 68 | allAssociations := a.Entity.AllAssociations() 69 | for i := range allAssociations { 70 | if allAssociations[i].Name() == name { 71 | asso := &ArgAssociation{ 72 | Association: allAssociations[i], 73 | ArgEntities: argEntitiesFromAssociation(allAssociations[i], ider), 74 | } 75 | 76 | a.Associations = append(a.Associations, asso) 77 | 78 | return asso 79 | } 80 | } 81 | panic("Can not find entity association:" + a.Entity.Name() + "." + name) 82 | } 83 | 84 | func (e *ArgEntity) Alise() string { 85 | return fmt.Sprintf("%s%d", PREFIX_T, e.Id) 86 | } 87 | 88 | func (a *ArgAssociation) GetTypeEntity(uuid string) *ArgEntity { 89 | entities := a.ArgEntities 90 | for i := range entities { 91 | if entities[i].Entity.Uuid() == uuid { 92 | return entities[i] 93 | } 94 | } 95 | 96 | panic("Can not find association entity by uuid") 97 | } 98 | 99 | func BuildArgEntity(entity *Entity, where interface{}, ider Ider) *ArgEntity { 100 | rootEntity := &ArgEntity{ 101 | Id: ider.CreateId(), 102 | Entity: entity, 103 | } 104 | if where != nil { 105 | if whereMap, ok := where.(QueryArg); ok { 106 | buildWhereEntity(rootEntity, whereMap, ider) 107 | } 108 | } 109 | return rootEntity 110 | } 111 | 112 | func buildWhereEntity(argEntity *ArgEntity, where QueryArg, ider Ider) { 113 | for key, value := range where { 114 | switch key { 115 | case consts.ARG_AND, consts.ARG_NOT, consts.ARG_OR: 116 | if subWhere, ok := value.(QueryArg); ok { 117 | buildWhereEntity(argEntity, subWhere, ider) 118 | } 119 | break 120 | default: 121 | association := argEntity.Entity.GetAssociationByName(key) 122 | if association != nil { 123 | argAssociation := argEntity.GetWithMakeAssociation(key, ider) 124 | if subWhere, ok := value.(QueryArg); ok { 125 | for i := range argAssociation.ArgEntities { 126 | buildWhereEntity(argAssociation.ArgEntities[i], subWhere, ider) 127 | } 128 | } 129 | } 130 | break 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /model/graph/args_test.go: -------------------------------------------------------------------------------- 1 | package graph 2 | -------------------------------------------------------------------------------- /model/graph/association.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/model/meta" 5 | ) 6 | 7 | type Association struct { 8 | Relation *Relation 9 | OwnerClassUuid string 10 | } 11 | 12 | type DerivedAssociation struct { 13 | Relation *DerivedRelation 14 | DerivedFrom *Association 15 | OwnerClassUuid string 16 | } 17 | 18 | func NewAssociation(r *Relation, ownerUuid string) *Association { 19 | return &Association{ 20 | Relation: r, 21 | OwnerClassUuid: ownerUuid, 22 | } 23 | } 24 | 25 | func (a *Association) Name() string { 26 | if a.IsSource() { 27 | return a.Relation.RoleOfTarget 28 | } else { 29 | return a.Relation.RoleOfSource 30 | } 31 | } 32 | 33 | func (a *Association) Owner() *Class { 34 | if a.IsSource() { 35 | return a.Relation.SourceClass() 36 | } else { 37 | return a.Relation.TargetClass() 38 | } 39 | } 40 | 41 | func (a *Association) TypeClass() *Class { 42 | if !a.IsSource() { 43 | return a.Relation.SourceClass() 44 | } else { 45 | return a.Relation.TargetClass() 46 | } 47 | } 48 | 49 | func (a *Association) TypeInterface() *Interface { 50 | if !a.IsSource() { 51 | return a.Relation.SourceInterface 52 | } else { 53 | return a.Relation.TargetInterface 54 | } 55 | } 56 | 57 | func (a *Association) TypeEntity() *Entity { 58 | if !a.IsSource() { 59 | return a.Relation.SourceEntity 60 | } else { 61 | return a.Relation.TargetEntity 62 | } 63 | } 64 | 65 | func (a *Association) TypePartial() *Partial { 66 | if !a.IsSource() { 67 | return a.Relation.SourcePartial 68 | } else { 69 | return a.Relation.TargetPartial 70 | } 71 | } 72 | 73 | func (a *Association) TypeExternal() *External { 74 | if !a.IsSource() { 75 | return a.Relation.SourceExternal 76 | } else { 77 | return a.Relation.TargetExternal 78 | } 79 | } 80 | 81 | func (a *Association) Description() string { 82 | if a.IsSource() { 83 | return a.Relation.DescriptionOnTarget 84 | } else { 85 | return a.Relation.DescriptionOnSource 86 | } 87 | } 88 | 89 | func (a *Association) IsArray() bool { 90 | if a.IsSource() { 91 | return a.Relation.TargetMultiplicity == meta.ZERO_MANY 92 | } else { 93 | return a.Relation.SourceMutiplicity == meta.ZERO_MANY 94 | } 95 | } 96 | 97 | func (a *Association) IsSource() bool { 98 | return a.Relation.SourceClass().Uuid() == a.OwnerClassUuid 99 | } 100 | 101 | func (a *Association) IsAbstract() bool { 102 | return len(a.Relation.Children) > 0 103 | } 104 | 105 | func (a *Association) DerivedAssociations() []*DerivedAssociation { 106 | associations := []*DerivedAssociation{} 107 | for i := range a.Relation.Children { 108 | derivedRelation := a.Relation.Children[i] 109 | ownerUuid := derivedRelation.SourceClass().Uuid() 110 | if a.Relation.TargetClass().Uuid() == a.OwnerClassUuid { 111 | ownerUuid = derivedRelation.TargetClass().Uuid() 112 | } 113 | associations = append(associations, &DerivedAssociation{ 114 | Relation: derivedRelation, 115 | DerivedFrom: a, 116 | OwnerClassUuid: ownerUuid, 117 | }) 118 | } 119 | return associations 120 | } 121 | 122 | func (a *Association) DerivedAssociationsByOwnerUuid(ownerUuid string) []*DerivedAssociation { 123 | associations := []*DerivedAssociation{} 124 | allDerived := a.DerivedAssociations() 125 | for i := range allDerived { 126 | if allDerived[i].OwnerClassUuid == ownerUuid { 127 | associations = append(associations, allDerived[i]) 128 | } 129 | } 130 | return associations 131 | } 132 | 133 | func (a *Association) GetName() string { 134 | return a.Name() 135 | } 136 | 137 | func (a *Association) Path() string { 138 | return a.Owner().Domain.Name + "." + a.Name() 139 | } 140 | 141 | //对手实体类 142 | func (d *DerivedAssociation) TypeClass() *Class { 143 | if d.Relation.SourceClass().Uuid() == d.OwnerClassUuid { 144 | return d.Relation.TargetClass() 145 | } else { 146 | return d.Relation.SourceClass() 147 | } 148 | 149 | } 150 | 151 | func (d *DerivedAssociation) Owner() *Class { 152 | if d.Relation.SourceClass().Uuid() == d.OwnerClassUuid { 153 | return d.Relation.SourceClass() 154 | } else { 155 | return d.Relation.TargetClass() 156 | } 157 | 158 | } 159 | 160 | func (d *DerivedAssociation) Name() string { 161 | if d.TypeClass().Uuid() == d.DerivedFrom.TypeClass().Uuid() { 162 | return d.DerivedFrom.Name() 163 | } else { 164 | return d.DerivedFrom.Name() + "For" + d.TypeClass().Name() 165 | } 166 | } 167 | 168 | func (a *DerivedAssociation) TypeEntity() *Entity { 169 | if !a.DerivedFrom.IsSource() { 170 | return a.Relation.SourceEntity 171 | } else { 172 | return a.Relation.TargetEntity 173 | } 174 | } 175 | 176 | func (a *DerivedAssociation) TypePartial() *Partial { 177 | if !a.DerivedFrom.IsSource() { 178 | return a.Relation.SourcePartial 179 | } else { 180 | return a.Relation.TargetPartial 181 | } 182 | } 183 | 184 | func (a *DerivedAssociation) TypeExternal() *External { 185 | if !a.DerivedFrom.IsSource() { 186 | return a.Relation.SourceExternal 187 | } else { 188 | return a.Relation.TargetExternal 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /model/graph/attribute.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import "rxdrag.com/entify/model/domain" 4 | 5 | type Attribute struct { 6 | domain.Attribute 7 | Class *Class 8 | EumnType *Enum 9 | EnityType *Entity 10 | ValueObjectType *Class 11 | } 12 | 13 | func NewAttribute(a *domain.Attribute, c *Class) *Attribute { 14 | return &Attribute{ 15 | Attribute: *a, 16 | Class: c, 17 | } 18 | } 19 | 20 | func (a *Attribute) GetName() string { 21 | return a.Attribute.Name 22 | } 23 | 24 | func (a *Attribute) GetType() string { 25 | return a.Attribute.Type 26 | } 27 | func (a *Attribute) GetEumnType() *Enum { 28 | return a.EumnType 29 | } 30 | func (a *Attribute) GetEnityType() *Entity { 31 | return a.EnityType 32 | } 33 | -------------------------------------------------------------------------------- /model/graph/class.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/consts" 5 | "rxdrag.com/entify/model/domain" 6 | "rxdrag.com/entify/utils" 7 | ) 8 | 9 | type Class struct { 10 | attributes []*Attribute 11 | associations []*Association 12 | methods []*Method 13 | Domain *domain.Class 14 | } 15 | 16 | func NewClass(c *domain.Class) *Class { 17 | cls := Class{ 18 | Domain: c, 19 | attributes: make([]*Attribute, len(c.Attributes)), 20 | methods: make([]*Method, len(c.Methods)), 21 | } 22 | 23 | for i := range c.Attributes { 24 | cls.attributes[i] = NewAttribute(c.Attributes[i], &cls) 25 | } 26 | 27 | for i := range c.Methods { 28 | cls.methods[i] = NewMethod(c.Methods[i], &cls) 29 | } 30 | 31 | return &cls 32 | } 33 | 34 | func (c *Class) Uuid() string { 35 | return c.Domain.Uuid 36 | } 37 | 38 | func (c *Class) InnerId() uint64 { 39 | return c.Domain.InnerId 40 | } 41 | 42 | func (c *Class) Name() string { 43 | return c.Domain.Name 44 | } 45 | 46 | func (c *Class) Description() string { 47 | return c.Domain.Description 48 | } 49 | 50 | func (c *Class) AddAssociation(a *Association) { 51 | c.associations = append(c.associations, a) 52 | } 53 | 54 | func (c *Class) TableName() string { 55 | return utils.SnakeString(c.Domain.Name) 56 | } 57 | 58 | // func (c *Class) Attributes() []*Attribute { 59 | // return c.attributes 60 | // } 61 | 62 | // func (c *Class) Associations() []*Association { 63 | // return c.associations 64 | // } 65 | 66 | func (c *Class) MethodsByType(operateType string) []*Method { 67 | methods := []*Method{} 68 | for i := range c.methods { 69 | method := c.methods[i] 70 | if method.Method.OperateType == operateType { 71 | methods = append(methods, method) 72 | } 73 | } 74 | 75 | return methods 76 | } 77 | 78 | func (c *Class) IsSoftDelete() bool { 79 | return c.Domain.SoftDelete 80 | } 81 | 82 | func (c *Class) QueryName() string { 83 | return utils.FirstLower(c.Name()) 84 | } 85 | 86 | func (c *Class) QueryOneName() string { 87 | return consts.ONE + utils.FirstUpper(c.Name()) 88 | } 89 | 90 | func (c *Class) QueryAggregateName() string { 91 | return utils.FirstLower(c.Name()) + utils.FirstUpper(consts.AGGREGATE) 92 | } 93 | 94 | func (c *Class) DeleteName() string { 95 | return consts.DELETE + utils.FirstUpper(c.Name()) 96 | } 97 | 98 | func (c *Class) DeleteByIdName() string { 99 | return consts.DELETE + utils.FirstUpper(c.Name()) + consts.BY_ID 100 | } 101 | 102 | func (c *Class) SetName() string { 103 | return consts.SET + utils.FirstUpper(c.Name()) 104 | } 105 | 106 | func (c *Class) UpsertName() string { 107 | return consts.UPSERT + utils.FirstUpper(c.Name()) 108 | } 109 | 110 | func (c *Class) UpsertOneName() string { 111 | return consts.UPSERT_ONE + utils.FirstUpper(c.Name()) 112 | } 113 | 114 | func (c *Class) AggregateName() string { 115 | return c.Name() + utils.FirstUpper(consts.AGGREGATE) 116 | } 117 | -------------------------------------------------------------------------------- /model/graph/entity.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/consts" 5 | "rxdrag.com/entify/model/domain" 6 | "rxdrag.com/entify/model/table" 7 | "rxdrag.com/entify/utils" 8 | ) 9 | 10 | type Entity struct { 11 | Class 12 | Table *table.Table 13 | Interfaces []*Interface 14 | } 15 | 16 | func NewEntity(c *domain.Class) *Entity { 17 | return &Entity{ 18 | Class: *NewClass(c), 19 | } 20 | } 21 | 22 | func (e *Entity) GetHasManyName() string { 23 | return utils.FirstUpper(consts.SET) + e.Name() + consts.HAS_MANY 24 | } 25 | 26 | func (e *Entity) GetHasOneName() string { 27 | return utils.FirstUpper(consts.SET) + e.Name() + consts.HAS_ONE 28 | } 29 | 30 | //有同名接口 31 | func (e *Entity) hasInterfaceWithSameName() bool { 32 | return e.Domain.HasChildren() 33 | } 34 | 35 | //包含继承来的 36 | func (e *Entity) AllAttributes() []*Attribute { 37 | attrs := []*Attribute{} 38 | attrs = append(attrs, e.attributes...) 39 | for i := range e.Interfaces { 40 | for j := range e.Interfaces[i].attributes { 41 | attr := e.Interfaces[i].attributes[j] 42 | if findAttribute(attr.Name, attrs) == nil { 43 | attrs = append(attrs, attr) 44 | } 45 | } 46 | } 47 | return attrs 48 | } 49 | 50 | func (e *Entity) AllMethods() []*Method { 51 | methods := []*Method{} 52 | methods = append(methods, e.methods...) 53 | for i := range e.Interfaces { 54 | for j := range e.Interfaces[i].methods { 55 | method := e.Interfaces[i].methods[j] 56 | if findMethod(method.GetName(), methods) == nil { 57 | methods = append(methods, method) 58 | } 59 | } 60 | } 61 | return methods 62 | } 63 | 64 | //包含继承来的 65 | func (e *Entity) AllAssociations() []*Association { 66 | associas := []*Association{} 67 | associas = append(associas, e.associations...) 68 | for i := range e.Interfaces { 69 | for j := range e.Interfaces[i].associations { 70 | asso := e.Interfaces[i].associations[j] 71 | if findAssociation(asso.Name(), associas) == nil { 72 | associas = append(associas, asso) 73 | } 74 | } 75 | } 76 | return associas 77 | } 78 | 79 | func (e *Entity) GetAssociationByName(name string) *Association { 80 | associations := e.AllAssociations() 81 | for i := range associations { 82 | if associations[i].Name() == name { 83 | return associations[i] 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (e *Entity) IsEmperty() bool { 91 | return len(e.AllAttributes()) < 1 && len(e.AllAssociations()) < 1 92 | } 93 | 94 | func (e *Entity) AllAttributeNames() []string { 95 | names := make([]string, len(e.AllAttributes())) 96 | 97 | for i, attr := range e.AllAttributes() { 98 | names[i] = attr.Name 99 | } 100 | 101 | return names 102 | } 103 | 104 | func (e *Entity) GetAttributeByName(name string) *Attribute { 105 | for _, attr := range e.AllAttributes() { 106 | if attr.Name == name { 107 | return attr 108 | } 109 | } 110 | 111 | return nil 112 | } 113 | 114 | func findAttribute(name string, attrs []*Attribute) *Attribute { 115 | for i := range attrs { 116 | if attrs[i].Name == name { 117 | return attrs[i] 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | func findMethod(name string, methods []*Method) *Method { 124 | for i := range methods { 125 | if methods[i].GetName() == name { 126 | return methods[i] 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | func findAssociation(name string, assos []*Association) *Association { 133 | for i := range assos { 134 | if assos[i].Name() == name { 135 | return assos[i] 136 | } 137 | } 138 | return nil 139 | } 140 | -------------------------------------------------------------------------------- /model/graph/enum.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/model/domain" 5 | ) 6 | 7 | type Enum struct { 8 | domain.Enum 9 | } 10 | 11 | func NewEnum(e *domain.Enum) *Enum { 12 | return &Enum{Enum: *e} 13 | } 14 | -------------------------------------------------------------------------------- /model/graph/external.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import "rxdrag.com/entify/model/domain" 4 | 5 | type External struct { 6 | Entity 7 | } 8 | 9 | func NewExternal(c *domain.Class) *External { 10 | return &External{ 11 | Entity: Entity{ 12 | Class: *NewClass(c), 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /model/graph/interface.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import "rxdrag.com/entify/model/domain" 4 | 5 | type Interface struct { 6 | Class 7 | Children []*Entity 8 | Parents []*Interface 9 | } 10 | 11 | func NewInterface(c *domain.Class) *Interface { 12 | return &Interface{ 13 | Class: *NewClass(c), 14 | } 15 | } 16 | 17 | func (f *Interface) IsInterface() bool { 18 | return true 19 | } 20 | func (f *Interface) Interface() *Interface { 21 | return f 22 | } 23 | func (f *Interface) Entity() *Entity { 24 | return nil 25 | } 26 | 27 | func (f *Interface) AllAttributes() []*Attribute { 28 | attrs := []*Attribute{} 29 | attrs = append(attrs, f.attributes...) 30 | for i := range f.Parents { 31 | for j := range f.Parents[i].attributes { 32 | attr := f.Parents[i].attributes[j] 33 | if findAttribute(attr.Name, attrs) == nil { 34 | attrs = append(attrs, attr) 35 | } 36 | } 37 | } 38 | return attrs 39 | } 40 | 41 | func (f *Interface) AllMethods() []*Method { 42 | methods := []*Method{} 43 | methods = append(methods, f.methods...) 44 | for i := range f.Parents { 45 | for j := range f.Parents[i].methods { 46 | method := f.Parents[i].methods[j] 47 | if findMethod(method.GetName(), methods) == nil { 48 | methods = append(methods, method) 49 | } 50 | } 51 | } 52 | return methods 53 | } 54 | 55 | func (f *Interface) AllAssociations() []*Association { 56 | associas := []*Association{} 57 | associas = append(associas, f.associations...) 58 | for i := range f.Parents { 59 | for j := range f.Parents[i].associations { 60 | asso := f.Parents[i].associations[j] 61 | if findAssociation(asso.Name(), associas) == nil { 62 | associas = append(associas, asso) 63 | } 64 | } 65 | } 66 | return associas 67 | } 68 | 69 | func (f *Interface) GetAssociationByName(name string) *Association { 70 | associations := f.AllAssociations() 71 | for i := range associations { 72 | if associations[i].Name() == name { 73 | return associations[i] 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func (f *Interface) IsEmperty() bool { 81 | return len(f.AllAttributes()) < 1 && len(f.AllAssociations()) < 1 82 | } 83 | 84 | func (f *Interface) AllAttributeNames() []string { 85 | names := make([]string, len(f.AllAttributes())) 86 | 87 | for i, attr := range f.AllAttributes() { 88 | names[i] = attr.Name 89 | } 90 | 91 | return names 92 | } 93 | 94 | func (f *Interface) GetAttributeByName(name string) *Attribute { 95 | for _, attr := range f.AllAttributes() { 96 | if attr.Name == name { 97 | return attr 98 | } 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /model/graph/method.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/model/domain" 5 | ) 6 | 7 | type Method struct { 8 | Method *domain.Method 9 | EumnType *Enum 10 | EnityType *Entity 11 | ValueObjectType *Class 12 | Class *Class 13 | } 14 | 15 | func NewMethod(m *domain.Method, c *Class) *Method { 16 | return &Method{ 17 | Method: m, 18 | Class: c, 19 | } 20 | } 21 | 22 | func (m *Method) Uuid() string { 23 | return m.Method.Uuid 24 | } 25 | 26 | func (m *Method) GetName() string { 27 | return m.Method.Name 28 | } 29 | 30 | func (m *Method) GetType() string { 31 | return m.Method.Type 32 | } 33 | func (m *Method) GetEumnType() *Enum { 34 | return m.EumnType 35 | } 36 | func (m *Method) GetEnityType() *Entity { 37 | return m.EnityType 38 | } 39 | -------------------------------------------------------------------------------- /model/graph/partial.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/consts" 5 | "rxdrag.com/entify/model/domain" 6 | "rxdrag.com/entify/utils" 7 | ) 8 | 9 | type Partial struct { 10 | Entity 11 | } 12 | 13 | func NewPartial(c *domain.Class) *Partial { 14 | return &Partial{ 15 | Entity: Entity{ 16 | Class: *NewClass(c), 17 | }, 18 | } 19 | } 20 | 21 | func (p *Partial) NameWithPartial() string { 22 | return p.Domain.Name + utils.FirstUpper(p.Domain.PartialName) 23 | } 24 | 25 | func (p *Partial) QueryName() string { 26 | return utils.FirstLower(p.NameWithPartial()) 27 | } 28 | 29 | func (p *Partial) QueryOneName() string { 30 | return consts.ONE + utils.FirstUpper(p.NameWithPartial()) 31 | } 32 | 33 | func (p *Partial) QueryAggregateName() string { 34 | return utils.FirstLower(p.NameWithPartial()) + utils.FirstUpper(consts.AGGREGATE) 35 | } 36 | 37 | func (p *Partial) DeleteName() string { 38 | return consts.DELETE + utils.FirstUpper(p.NameWithPartial()) 39 | } 40 | 41 | func (p *Partial) DeleteByIdName() string { 42 | return consts.DELETE + utils.FirstUpper(p.NameWithPartial()) + consts.BY_ID 43 | } 44 | 45 | func (p *Partial) SetName() string { 46 | return consts.SET + utils.FirstUpper(p.NameWithPartial()) 47 | } 48 | 49 | func (p *Partial) InsertName() string { 50 | return consts.INSERT + utils.FirstUpper(p.NameWithPartial()) 51 | } 52 | 53 | func (p *Partial) InsertOneName() string { 54 | return consts.INSERT_ONE + utils.FirstUpper(p.NameWithPartial()) 55 | } 56 | 57 | func (p *Partial) UpdateName() string { 58 | return consts.UPDATE + utils.FirstUpper(p.NameWithPartial()) 59 | } 60 | 61 | func (p *Partial) UpdateOneName() string { 62 | return consts.UPDATE_ONE + utils.FirstUpper(p.NameWithPartial()) 63 | } 64 | 65 | func (p *Partial) AggregateName() string { 66 | return p.NameWithPartial() + utils.FirstUpper(consts.AGGREGATE) 67 | } 68 | 69 | func (p *Partial) GetHasManyName() string { 70 | return utils.FirstUpper(consts.SET) + p.NameWithPartial() + consts.HAS_MANY 71 | } 72 | 73 | func (p *Partial) GetHasOneName() string { 74 | return utils.FirstUpper(consts.SET) + p.NameWithPartial() + consts.HAS_ONE 75 | } 76 | -------------------------------------------------------------------------------- /model/graph/propertier.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | type Propertier interface { 4 | GetName() string 5 | GetType() string 6 | GetEumnType() *Enum 7 | GetEnityType() *Entity 8 | } 9 | -------------------------------------------------------------------------------- /model/graph/relation.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "rxdrag.com/entify/model/domain" 5 | "rxdrag.com/entify/model/meta" 6 | "rxdrag.com/entify/model/table" 7 | ) 8 | 9 | type Relation struct { 10 | Uuid string 11 | InnerId uint64 12 | RelationType string 13 | SourceInterface *Interface 14 | TargetInterface *Interface 15 | SourceEntity *Entity 16 | TargetEntity *Entity 17 | SourcePartial *Partial 18 | TargetPartial *Partial 19 | SourceExternal *External 20 | TargetExternal *External 21 | RoleOfTarget string 22 | RoleOfSource string 23 | DescriptionOnSource string 24 | DescriptionOnTarget string 25 | SourceMutiplicity string 26 | TargetMultiplicity string 27 | EnableAssociaitonClass bool 28 | AssociationClass meta.AssociationClass 29 | Children []*DerivedRelation 30 | Table *table.Table 31 | } 32 | 33 | type DerivedRelation struct { 34 | Parent *Relation 35 | SourceEntity *Entity 36 | TargetEntity *Entity 37 | SourcePartial *Partial 38 | TargetPartial *Partial 39 | SourceExternal *External 40 | TargetExternal *External 41 | Table *table.Table 42 | } 43 | 44 | func NewRelation( 45 | r *domain.Relation, 46 | sourceInterface *Interface, 47 | targetInterface *Interface, 48 | sourceEntity *Entity, 49 | targetEntity *Entity, 50 | sourcePartial *Partial, 51 | targetPartial *Partial, 52 | sourceExternal *External, 53 | targetExternal *External, 54 | ) *Relation { 55 | relation := &Relation{ 56 | Uuid: r.Uuid, 57 | InnerId: r.InnerId, 58 | RelationType: r.RelationType, 59 | SourceInterface: sourceInterface, 60 | TargetInterface: targetInterface, 61 | SourceEntity: sourceEntity, 62 | TargetEntity: targetEntity, 63 | SourcePartial: sourcePartial, 64 | TargetPartial: targetPartial, 65 | SourceExternal: sourceExternal, 66 | TargetExternal: targetExternal, 67 | RoleOfTarget: r.RoleOfTarget, 68 | RoleOfSource: r.RoleOfSource, 69 | DescriptionOnSource: r.DescriptionOnSource, 70 | DescriptionOnTarget: r.DescriptionOnTarget, 71 | SourceMutiplicity: r.SourceMutiplicity, 72 | TargetMultiplicity: r.TargetMultiplicity, 73 | EnableAssociaitonClass: r.EnableAssociaitonClass, 74 | AssociationClass: r.AssociationClass, 75 | } 76 | 77 | return relation 78 | } 79 | 80 | func (r *Relation) SourceClass() *Class { 81 | if r.SourceInterface != nil { 82 | return &r.SourceInterface.Class 83 | } 84 | 85 | if r.SourceEntity != nil { 86 | return &r.SourceEntity.Class 87 | } 88 | 89 | if r.SourcePartial != nil { 90 | return &r.SourcePartial.Class 91 | } 92 | if r.SourceExternal != nil { 93 | return &r.SourceExternal.Class 94 | } 95 | return nil 96 | } 97 | 98 | func (r *Relation) TargetClass() *Class { 99 | if r.TargetInterface != nil { 100 | return &r.TargetInterface.Class 101 | } 102 | 103 | if r.TargetEntity != nil { 104 | return &r.TargetEntity.Class 105 | } 106 | 107 | if r.TargetPartial != nil { 108 | return &r.TargetPartial.Class 109 | } 110 | if r.TargetExternal != nil { 111 | return &r.TargetExternal.Class 112 | } 113 | return nil 114 | } 115 | 116 | func (r *Relation) IsRealRelation() bool { 117 | if r.SourceInterface != nil || r.TargetInterface != nil { 118 | return false 119 | } 120 | 121 | return true 122 | } 123 | 124 | func (r *DerivedRelation) SourceClass() *Class { 125 | 126 | if r.SourceEntity != nil { 127 | return &r.SourceEntity.Class 128 | } 129 | 130 | if r.SourcePartial != nil { 131 | return &r.SourcePartial.Class 132 | } 133 | if r.SourceExternal != nil { 134 | return &r.SourceExternal.Class 135 | } 136 | return nil 137 | } 138 | 139 | func (r *DerivedRelation) TargetClass() *Class { 140 | if r.TargetEntity != nil { 141 | return &r.TargetEntity.Class 142 | } 143 | 144 | if r.TargetPartial != nil { 145 | return &r.TargetPartial.Class 146 | } 147 | if r.TargetExternal != nil { 148 | return &r.TargetExternal.Class 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /model/graph/table.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/consts" 7 | "rxdrag.com/entify/model/meta" 8 | "rxdrag.com/entify/model/table" 9 | ) 10 | 11 | func NewEntityTable(entity *Entity, partial bool) *table.Table { 12 | table := &table.Table{ 13 | Uuid: entity.Uuid(), 14 | Name: entity.TableName(), 15 | EntityInnerId: entity.Domain.InnerId, 16 | Partial: false, 17 | } 18 | 19 | allAttrs := entity.AllAttributes() 20 | for i := range allAttrs { 21 | attr := allAttrs[i] 22 | table.Columns = append(table.Columns, NewAttributeColumn(attr, partial)) 23 | } 24 | 25 | entity.Table = table 26 | return table 27 | } 28 | 29 | func NewAttributeColumn(attr *Attribute, partial bool) *table.Column { 30 | return &table.Column{ 31 | AttributeMeta: attr.AttributeMeta, 32 | PartialId: partial && attr.Name == consts.ID, 33 | } 34 | } 35 | 36 | func NewRelationTables(relation *Relation) []*table.Table { 37 | var tables []*table.Table 38 | name := fmt.Sprintf( 39 | "%s_%d_%d_%d", 40 | consts.PIVOT, 41 | relation.SourceClass().InnerId(), 42 | relation.InnerId, 43 | relation.TargetClass().InnerId(), 44 | ) 45 | if relation.IsRealRelation() { 46 | tab := &table.Table{ 47 | Uuid: relation.SourceClass().Uuid() + relation.Uuid + relation.TargetClass().Uuid(), 48 | Name: name, 49 | Columns: []*table.Column{ 50 | { 51 | AttributeMeta: meta.AttributeMeta{ 52 | Type: meta.ID, 53 | Uuid: relation.SourceClass().Uuid() + relation.Uuid, 54 | Name: relation.SourceClass().TableName(), 55 | Index: true, 56 | }, 57 | }, 58 | { 59 | AttributeMeta: meta.AttributeMeta{ 60 | Type: meta.ID, 61 | Uuid: relation.TargetClass().Uuid() + relation.Uuid, 62 | Name: relation.TargetClass().TableName(), 63 | Index: true, 64 | }, 65 | }, 66 | }, 67 | PKString: fmt.Sprintf("%s,%s", relation.SourceClass().TableName(), relation.TargetClass().TableName()), 68 | } 69 | if relation.EnableAssociaitonClass { 70 | for i := range relation.AssociationClass.Attributes { 71 | tab.Columns = append(tab.Columns, &table.Column{ 72 | AttributeMeta: relation.AssociationClass.Attributes[i], 73 | }) 74 | } 75 | } 76 | relation.Table = tab 77 | tables = append(tables, tab) 78 | } else { 79 | for i := range relation.Children { 80 | derivied := relation.Children[i] 81 | tables = append(tables, NewDerivedRelationTable(derivied)) 82 | } 83 | } 84 | 85 | return tables 86 | } 87 | 88 | func NewDerivedRelationTable(derived *DerivedRelation) *table.Table { 89 | name := fmt.Sprintf( 90 | "%s_%d_%d_%d", 91 | consts.PIVOT, 92 | derived.SourceClass().InnerId(), 93 | derived.Parent.InnerId, 94 | derived.TargetClass().InnerId(), 95 | ) 96 | tab := &table.Table{ 97 | Uuid: derived.SourceClass().Uuid() + derived.Parent.Uuid + derived.TargetClass().Uuid(), 98 | Name: name, 99 | Columns: []*table.Column{ 100 | { 101 | AttributeMeta: meta.AttributeMeta{ 102 | Type: meta.ID, 103 | Uuid: derived.SourceClass().Uuid() + derived.Parent.Uuid, 104 | Name: derived.SourceClass().TableName(), 105 | Index: true, 106 | }, 107 | }, 108 | { 109 | AttributeMeta: meta.AttributeMeta{ 110 | Type: meta.ID, 111 | Uuid: derived.TargetClass().Uuid() + derived.Parent.Uuid, 112 | Name: derived.TargetClass().TableName(), 113 | Index: true, 114 | }, 115 | }, 116 | }, 117 | PKString: fmt.Sprintf("%s,%s", derived.SourceClass().TableName(), derived.TargetClass().TableName()), 118 | } 119 | if derived.Parent.EnableAssociaitonClass { 120 | for i := range derived.Parent.AssociationClass.Attributes { 121 | tab.Columns = append(tab.Columns, &table.Column{ 122 | AttributeMeta: derived.Parent.AssociationClass.Attributes[i], 123 | }) 124 | } 125 | } 126 | derived.Table = tab 127 | return tab 128 | } 129 | -------------------------------------------------------------------------------- /model/meta/attributemeta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | type AttributeMeta struct { 4 | Uuid string `json:"uuid"` 5 | Type string `json:"type"` 6 | Primary bool `json:"primary"` 7 | Name string `json:"name"` 8 | Nullable bool `json:"nullable"` 9 | Default string `json:"default"` 10 | Unique bool `json:"unique"` 11 | Index bool `json:"index"` 12 | CreateDate bool `json:"createDate"` 13 | UpdateDate bool `json:"updateDate"` 14 | DeleteDate bool `json:"deleteDate"` 15 | Select bool `json:"select"` 16 | Length int `json:"length"` 17 | FloatM int `json:"floatM"` //M digits in total 18 | FloatD int `json:"floatD"` //D digits may be after the decimal point 19 | Unsigned bool `json:"unsigned"` 20 | TypeUuid string `json:"typeUuid"` 21 | Readonly bool `json:"readonly"` 22 | Description string `json:"description"` 23 | TypeLabel string `json:"typeLabel"` 24 | System bool `json:"system"` 25 | } 26 | -------------------------------------------------------------------------------- /model/meta/classmeta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | const ( 4 | CLASSS_ENTITY string = "Entity" 5 | CLASSS_ENUM string = "Enum" 6 | CLASSS_ABSTRACT string = "Abstract" 7 | CLASS_VALUE_OBJECT string = "ValueObject" 8 | CLASS_EXTERNAL string = "External" 9 | CLASS_PARTIAL string = "Partial" 10 | ) 11 | 12 | type ClassMeta struct { 13 | Uuid string `json:"uuid"` 14 | InnerId uint64 `json:"innerId"` 15 | Name string `json:"name"` 16 | PartialName string `json:"partialName"` 17 | StereoType string `json:"stereoType"` 18 | Attributes []AttributeMeta `json:"attributes"` 19 | Methods []MethodMeta `json:"methods"` 20 | Root bool `json:"root"` 21 | Description string `json:"description"` 22 | SoftDelete bool `json:"softDelete"` 23 | System bool `json:"system"` 24 | } 25 | -------------------------------------------------------------------------------- /model/meta/content.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | type MetaContent struct { 4 | Classes []ClassMeta `json:"entities"` 5 | Relations []RelationMeta `json:"relations"` 6 | Diagrams []interface{} `json:"diagrams"` 7 | X6Nodes []interface{} `json:"x6Nodes"` 8 | X6Edges []interface{} `json:"x6Edges"` 9 | } 10 | -------------------------------------------------------------------------------- /model/meta/meta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | type Model struct { 4 | Classes []*ClassMeta 5 | Relations []*RelationMeta 6 | } 7 | 8 | func New(m *MetaContent) *Model { 9 | model := Model{ 10 | Classes: make([]*ClassMeta, len(m.Classes)), 11 | Relations: make([]*RelationMeta, len(m.Relations)), 12 | } 13 | 14 | for i := range m.Classes { 15 | model.Classes[i] = &m.Classes[i] 16 | } 17 | 18 | for i := range m.Relations { 19 | model.Relations[i] = &m.Relations[i] 20 | } 21 | return &model 22 | } 23 | -------------------------------------------------------------------------------- /model/meta/methodmeta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | const ( 4 | SCRIPT string = "script" 5 | CLOUD_FUNCTION string = "cloudFunction" 6 | MICRO_SERVICE string = "microService" 7 | 8 | QUERY string = "query" 9 | MUTATION string = "mutation" 10 | ) 11 | 12 | type ArgMeta struct { 13 | Uuid string `json:"uuid"` 14 | Type string `json:"type"` 15 | Name string `json:"name"` 16 | TypeUuid string `json:"typeUuid"` 17 | TypeLabel string `json:"typeLabel"` 18 | } 19 | 20 | type MethodMeta struct { 21 | Uuid string `json:"uuid"` 22 | Name string `json:"name"` 23 | Type string `json:"type"` 24 | TypeUuid string `json:"typeUuid"` 25 | TypeLabel string `json:"typeLabel"` 26 | Args []ArgMeta `json:"args"` 27 | OperateType string `json:"operateType"` //Mutation or Query 28 | ImplementType string `json:"implementType"` 29 | MethodImplements string `json:"methodImplements"` 30 | Description string `json:"description"` 31 | } 32 | -------------------------------------------------------------------------------- /model/meta/predefined.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "rxdrag.com/entify/consts" 5 | ) 6 | 7 | const ( 8 | META_STATUS_PUBLISHED string = "published" 9 | META_STATUS_CANCELLED string = "cancelled" 10 | META_STATUS_MIGRATION_ERROR string = "migrationError" 11 | META_STATUS_ROLLBACK_ERROR string = "rollbackError" 12 | META_STATUS_ENUM_UUID string = "META_STATUS_ENUM_UUID" 13 | 14 | META_ABILITY_TYPE_CREATE string = "create" 15 | META_ABILITY_TYPE_READ string = "read" 16 | META_ABILITY_TYPE_UPDATE string = "update" 17 | META_ABILITY_TYPE_DELETE string = "delete" 18 | META_ABILITY_TYPE_ENUM_UUID string = "META_ABILITY_TYPE_ENUM_UUID" 19 | 20 | META_ENTITY_UUID string = "META_ENTITY_UUID" 21 | ) 22 | 23 | var MetaStatusEnum = ClassMeta{ 24 | Uuid: META_STATUS_ENUM_UUID, 25 | Name: "MetaStatus", 26 | StereoType: ENUM, 27 | Attributes: []AttributeMeta{ 28 | { 29 | Name: META_STATUS_PUBLISHED, 30 | }, 31 | { 32 | Name: META_STATUS_CANCELLED, 33 | }, 34 | { 35 | Name: META_STATUS_MIGRATION_ERROR, 36 | }, 37 | { 38 | Name: META_STATUS_ROLLBACK_ERROR, 39 | }, 40 | }, 41 | } 42 | 43 | var MetaClass = ClassMeta{ 44 | Uuid: META_ENTITY_UUID, 45 | Name: consts.META_ENTITY_NAME, 46 | InnerId: consts.META_INNER_ID, 47 | StereoType: CLASSS_ENTITY, 48 | Root: true, 49 | Attributes: []AttributeMeta{ 50 | { 51 | Uuid: "META_COLUMN_ID_UUID", 52 | Type: ID, 53 | Name: consts.META_ID, 54 | }, 55 | { 56 | Uuid: "META_COLUMN_CONTENT_UUID", 57 | Type: VALUE_OBJECT, 58 | Name: consts.META_CONTENT, 59 | }, 60 | { 61 | Uuid: "META_COLUMN_STATUS_UUID", 62 | Type: ENUM, 63 | Name: consts.META_STATUS, 64 | TypeUuid: META_STATUS_ENUM_UUID, 65 | }, 66 | { 67 | Uuid: "META_COLUMN_PUBLISHED_AT_UUID", 68 | Type: DATE, 69 | Name: consts.META_PUBLISHEDAT, 70 | }, 71 | { 72 | Uuid: "META_COLUMN_CREATED_AT_UUID", 73 | Type: DATE, 74 | Name: consts.META_CREATEDAT, 75 | }, 76 | { 77 | Uuid: "META_COLUMN_UPDATED_AT_UUID", 78 | Type: DATE, 79 | Name: consts.META_UPDATEDAT, 80 | }, 81 | }, 82 | } 83 | 84 | var EntityAuthSettingsClass = ClassMeta{ 85 | Name: "EntityAuthSettings", 86 | Uuid: "META_ENTITY_AUTH_SETTINGS_UUID", 87 | InnerId: consts.ENTITY_AUTH_SETTINGS_INNER_ID, 88 | Root: true, 89 | System: true, 90 | Attributes: []AttributeMeta{ 91 | { 92 | Name: consts.ID, 93 | Type: ID, 94 | Uuid: "RX_ENTITY_AUTH_SETTINGS_ID_UUID", 95 | Primary: true, 96 | System: true, 97 | }, 98 | { 99 | Name: "entityUuid", 100 | Type: "String", 101 | Uuid: "RX_ENTITY_AUTH_SETTINGS_ENTITY_UUID_UUID", 102 | System: true, 103 | Unique: true, 104 | }, 105 | { 106 | Name: "expand", 107 | Type: "Boolean", 108 | Uuid: "RX_ENTITY_AUTH_SETTINGS_EXPAND_UUID", 109 | System: true, 110 | }, 111 | }, 112 | StereoType: "Entity", 113 | } 114 | 115 | var AbilityTypeEnum = ClassMeta{ 116 | Uuid: META_ABILITY_TYPE_ENUM_UUID, 117 | Name: "AbilityType", 118 | StereoType: ENUM, 119 | Attributes: []AttributeMeta{ 120 | { 121 | Name: META_ABILITY_TYPE_CREATE, 122 | }, 123 | { 124 | Name: META_ABILITY_TYPE_READ, 125 | }, 126 | { 127 | Name: META_ABILITY_TYPE_UPDATE, 128 | }, 129 | { 130 | Name: META_ABILITY_TYPE_DELETE, 131 | }, 132 | }, 133 | } 134 | 135 | var AbilityClass = ClassMeta{ 136 | Name: "Ability", 137 | Uuid: consts.ABILITY_UUID, 138 | InnerId: consts.Ability_INNER_ID, 139 | Root: true, 140 | System: true, 141 | Attributes: []AttributeMeta{ 142 | { 143 | Name: consts.ID, 144 | Type: ID, 145 | Uuid: "RX_ABILITY_ID_UUID", 146 | Primary: true, 147 | System: true, 148 | }, 149 | { 150 | Name: "entityUuid", 151 | Type: "String", 152 | Uuid: "RX_ABILITY_ENTITY_UUID_UUID", 153 | System: true, 154 | }, 155 | { 156 | Name: "columnUuid", 157 | Type: "String", 158 | Uuid: "RX_ABILITY_COLUMN_UUID_UUID", 159 | System: true, 160 | }, 161 | { 162 | Name: "can", 163 | Type: "Boolean", 164 | Uuid: "RX_ABILITY_CAN_UUID", 165 | System: true, 166 | }, 167 | { 168 | Name: "expression", 169 | Type: "String", 170 | Uuid: "RX_ABILITY_EXPRESSION_UUID", 171 | Length: 2000, 172 | System: true, 173 | }, 174 | { 175 | Name: "abilityType", 176 | Type: ENUM, 177 | Uuid: "RX_ABILITY_ABILITYTYPE_UUID", 178 | System: true, 179 | TypeUuid: META_ABILITY_TYPE_ENUM_UUID, 180 | }, 181 | { 182 | Name: "roleId", 183 | Type: ID, 184 | Uuid: "RX_ABILITY_ROLE_ID_UUID", 185 | System: true, 186 | }, 187 | }, 188 | StereoType: "Entity", 189 | } 190 | -------------------------------------------------------------------------------- /model/meta/relationmeta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | const ( 4 | INHERIT string = "inherit" 5 | TWO_WAY_ASSOCIATION string = "twoWayAssociation" 6 | TWO_WAY_AGGREGATION string = "twoWayAggregation" 7 | TWO_WAY_COMBINATION string = "twoWayCombination" 8 | ONE_WAY_ASSOCIATION string = "oneWayAssociation" 9 | ONE_WAY_AGGREGATION string = "oneWayAggregation" 10 | ONE_WAY_COMBINATION string = "oneWayCombination" 11 | 12 | ZERO_ONE string = "0..1" 13 | ZERO_MANY string = "0..*" 14 | ) 15 | 16 | type AssociationClass struct { 17 | Name string `json:"name"` 18 | Attributes []AttributeMeta `json:"attributes"` 19 | } 20 | 21 | type RelationMeta struct { 22 | Uuid string `json:"uuid"` 23 | InnerId uint64 `json:"innerId"` 24 | RelationType string `json:"relationType"` 25 | SourceId string `json:"sourceId"` 26 | TargetId string `json:"targetId"` 27 | RoleOfTarget string `json:"roleOfTarget"` 28 | RoleOfSource string `json:"roleOfSource"` 29 | DescriptionOnSource string `json:"descriptionOnSource"` 30 | DescriptionOnTarget string `json:"descriptionOnTarget"` 31 | SourceMutiplicity string `json:"sourceMutiplicity"` 32 | TargetMultiplicity string `json:"targetMultiplicity"` 33 | EnableAssociaitonClass bool `json:"enableAssociaitonClass"` 34 | AssociationClass AssociationClass `json:"associationClass"` 35 | System bool `json:"system"` 36 | } 37 | -------------------------------------------------------------------------------- /model/meta/type.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "rxdrag.com/entify/consts" 4 | 5 | const ( 6 | ID string = "ID" 7 | INT string = "Int" 8 | FLOAT string = "Float" 9 | BOOLEAN string = "Boolean" 10 | STRING string = "String" 11 | DATE string = "Date" 12 | ENUM string = "Enum" 13 | VALUE_OBJECT string = "ValueObject" 14 | ENTITY string = "Entity" 15 | ID_ARRAY string = "ID[]" 16 | INT_ARRAY string = "Int[]" 17 | FLOAT_ARRAY string = "Float[]" 18 | STRING_ARRAY string = "String[]" 19 | DATE_ARRAY string = "Date[]" 20 | ENUM_ARRAY string = "EnumArray" 21 | VALUE_OBJECT_ARRAY string = "ValueObjectArray" 22 | ENTITY_ARRAY string = "EntityArray" 23 | FILE string = consts.FILE 24 | ) 25 | -------------------------------------------------------------------------------- /model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/domain" 6 | "rxdrag.com/entify/model/graph" 7 | "rxdrag.com/entify/model/meta" 8 | ) 9 | 10 | type Model struct { 11 | Meta *meta.Model 12 | Domain *domain.Model 13 | Graph *graph.Model 14 | Schema *graphql.Schema 15 | } 16 | 17 | func New(c *meta.MetaContent) *Model { 18 | metaModel := meta.New(c) 19 | domainModel := domain.New(metaModel) 20 | grahpModel := graph.New(domainModel) 21 | model := Model{ 22 | Meta: metaModel, 23 | Domain: domainModel, 24 | Graph: grahpModel, 25 | Schema: nil, 26 | } 27 | return &model 28 | } 29 | 30 | var GlobalModel *Model 31 | -------------------------------------------------------------------------------- /model/table/column.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import "rxdrag.com/entify/model/meta" 4 | 5 | type Column struct { 6 | meta.AttributeMeta 7 | PartialId bool 8 | Key bool 9 | } 10 | -------------------------------------------------------------------------------- /model/table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | type Table struct { 4 | Uuid string 5 | Name string 6 | EntityInnerId uint64 7 | Columns []*Column 8 | PKString string 9 | Partial bool 10 | } 11 | -------------------------------------------------------------------------------- /repository/authorization.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/graphql-go/graphql" 7 | "github.com/mitchellh/mapstructure" 8 | "rxdrag.com/entify/authcontext" 9 | "rxdrag.com/entify/common" 10 | "rxdrag.com/entify/consts" 11 | "rxdrag.com/entify/model" 12 | "rxdrag.com/entify/model/graph" 13 | "rxdrag.com/entify/model/meta" 14 | ) 15 | 16 | type AbilityVerifier struct { 17 | me *common.User 18 | RoleIds []string 19 | Abilities []*common.Ability 20 | // expression Key : 从Auth模块返回的结果 21 | QueryUserCache map[string][]common.User 22 | isSupper bool 23 | } 24 | 25 | func NewVerifier() *AbilityVerifier { 26 | verifier := AbilityVerifier{} 27 | 28 | return &verifier 29 | } 30 | 31 | func NewSupperVerifier() *AbilityVerifier { 32 | verifier := AbilityVerifier{isSupper: true} 33 | 34 | return &verifier 35 | } 36 | 37 | func (v *AbilityVerifier) Init(p graphql.ResolveParams, entityUuids []string) { 38 | me := authcontext.ParseContextValues(p).Me 39 | v.me = me 40 | if me != nil { 41 | for i := range me.Roles { 42 | v.RoleIds = append(v.RoleIds, me.Roles[i].Id) 43 | } 44 | } else { 45 | v.RoleIds = append(v.RoleIds, consts.GUEST_ROLE_ID) 46 | } 47 | 48 | v.queryRolesAbilities(entityUuids) 49 | } 50 | 51 | func (v *AbilityVerifier) IsSupper() bool { 52 | if v.isSupper { 53 | return true 54 | } 55 | 56 | if v.me != nil { 57 | return v.me.IsSupper 58 | } 59 | 60 | return false 61 | } 62 | 63 | func (v *AbilityVerifier) IsDemo() bool { 64 | if v.me != nil { 65 | return v.me.IsDemo 66 | } 67 | 68 | return false 69 | } 70 | 71 | func (v *AbilityVerifier) WeaveAuthInArgs(entityUuid string, args interface{}) interface{} { 72 | if v.IsSupper() || v.IsDemo() { 73 | return args 74 | } 75 | 76 | var rootAnd []map[string]interface{} 77 | 78 | if args == nil { 79 | rootAnd = []map[string]interface{}{} 80 | } else { 81 | argsMap := args.(map[string]interface{}) 82 | if argsMap[consts.ARG_AND] == nil { 83 | rootAnd = []map[string]interface{}{} 84 | } else { 85 | rootAnd = argsMap[consts.ARG_AND].([]map[string]interface{}) 86 | } 87 | } 88 | 89 | // if len(v.Abilities) == 0 && !v.IsSupper() && !v.IsDemo() { 90 | // rootAnd = append(rootAnd, map[string]interface{}{ 91 | // consts.ID: map[string]interface{}{ 92 | // consts.ARG_EQ: 0, 93 | // }, 94 | // }) 95 | 96 | // return map[string]interface{}{ 97 | // consts.ARG_AND: rootAnd, 98 | // } 99 | // } 100 | 101 | expArg := v.queryEntityArgsMap(entityUuid) 102 | if len(expArg) > 0 { 103 | rootAnd = append(rootAnd, expArg) 104 | } 105 | 106 | if args == nil { 107 | return map[string]interface{}{ 108 | consts.ARG_AND: rootAnd, 109 | } 110 | } else { 111 | argsMap := args.(map[string]interface{}) 112 | argsMap[consts.ARG_AND] = rootAnd 113 | return argsMap 114 | } 115 | } 116 | 117 | func (v *AbilityVerifier) CanReadEntity(entityUuid string) bool { 118 | if v.IsSupper() || v.IsDemo() { 119 | return true 120 | } 121 | 122 | for _, ability := range v.Abilities { 123 | if ability.EntityUuid == entityUuid && 124 | ability.ColumnUuid == "" && 125 | ability.Can && 126 | ability.AbilityType == meta.META_ABILITY_TYPE_READ { 127 | return true 128 | } 129 | } 130 | return false 131 | } 132 | 133 | func (v *AbilityVerifier) EntityMutationCan(entityData map[string]interface{}) bool { 134 | return false 135 | } 136 | 137 | func (v *AbilityVerifier) FieldCan(entityData map[string]interface{}) bool { 138 | return false 139 | } 140 | 141 | func (v *AbilityVerifier) queryEntityArgsMap(entityUuid string) map[string]interface{} { 142 | expMap := map[string]interface{}{} 143 | queryEntityExpressions := []string{} 144 | 145 | for _, ability := range v.Abilities { 146 | if ability.EntityUuid == entityUuid && 147 | ability.ColumnUuid == "" && 148 | ability.Can && 149 | ability.AbilityType == meta.META_ABILITY_TYPE_READ && 150 | ability.Expression != "" { 151 | queryEntityExpressions = append(queryEntityExpressions, ability.Expression) 152 | } 153 | } 154 | if len(queryEntityExpressions) > 0 { 155 | expMap[consts.ARG_OR] = expressionArrayToArgs(queryEntityExpressions) 156 | } 157 | return expMap 158 | } 159 | 160 | func expressionToKey(expression string) string { 161 | return "" 162 | } 163 | 164 | func expressionArrayToArgs(expressionArray []string) []map[string]interface{} { 165 | var args []map[string]interface{} 166 | for _, expression := range expressionArray { 167 | args = append(args, expressionToArg(expression)) 168 | } 169 | return args 170 | } 171 | 172 | func expressionToArg(expression string) map[string]interface{} { 173 | arg := map[string]interface{}{} 174 | err := json.Unmarshal([]byte(expression), &arg) 175 | if err != nil { 176 | panic("Parse authorization expression error:" + err.Error()) 177 | } 178 | return arg 179 | } 180 | 181 | func (v *AbilityVerifier) queryRolesAbilities(entityUuids []string) { 182 | abilities := QueryEntity(model.GlobalModel.Graph.GetEntityByUuid(consts.ABILITY_UUID), graph.QueryArg{ 183 | consts.ARG_WHERE: graph.QueryArg{ 184 | "roleId": graph.QueryArg{ 185 | consts.ARG_IN: v.RoleIds, 186 | }, 187 | // "abilityType": QueryArg{ 188 | // consts.ARG_EQ: v.AbilityType, 189 | // }, 190 | "entityUuid": graph.QueryArg{ 191 | consts.ARG_IN: entityUuids, 192 | }, 193 | }, 194 | }, NewSupperVerifier()) 195 | 196 | for _, abilityMap := range abilities { 197 | var ability common.Ability 198 | err := mapstructure.Decode(abilityMap, &ability) 199 | if err != nil { 200 | panic(err.Error()) 201 | } 202 | v.Abilities = append(v.Abilities, &ability) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /repository/connection.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "rxdrag.com/entify/config" 5 | "rxdrag.com/entify/db" 6 | ) 7 | 8 | type Connection struct { 9 | idSeed int //use for sql join table 10 | Dbx *db.Dbx 11 | v *AbilityVerifier 12 | } 13 | 14 | func openWithConfig(cfg config.DbConfig, v *AbilityVerifier) (*Connection, error) { 15 | dbx, err := db.Open(cfg.Driver, DbString(cfg)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | con := Connection{ 20 | idSeed: 1, 21 | Dbx: dbx, 22 | v: v, 23 | } 24 | return &con, err 25 | } 26 | 27 | func Open(v *AbilityVerifier) (*Connection, error) { 28 | cfg := config.GetDbConfig() 29 | return openWithConfig(cfg, v) 30 | } 31 | 32 | func (c *Connection) BeginTx() error { 33 | return c.Dbx.BeginTx() 34 | } 35 | 36 | func (c *Connection) Commit() error { 37 | return c.Dbx.Commit() 38 | } 39 | 40 | func (c *Connection) ClearTx() { 41 | c.Dbx.ClearTx() 42 | } 43 | 44 | //use for sql join table 45 | func (c *Connection) CreateId() int { 46 | c.idSeed++ 47 | return c.idSeed 48 | } 49 | -------------------------------------------------------------------------------- /repository/convert.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | 7 | "github.com/mitchellh/mapstructure" 8 | "rxdrag.com/entify/db" 9 | "rxdrag.com/entify/model/data" 10 | "rxdrag.com/entify/model/graph" 11 | "rxdrag.com/entify/model/meta" 12 | "rxdrag.com/entify/storage" 13 | "rxdrag.com/entify/utils" 14 | ) 15 | 16 | func makeSaveValues(fields []*data.Field) []interface{} { 17 | objValues := make([]interface{}, 0, len(fields)) 18 | for _, field := range fields { 19 | value := field.Value 20 | column := field.Column 21 | 22 | if column.Type == meta.VALUE_OBJECT || 23 | column.Type == meta.ID_ARRAY || 24 | column.Type == meta.INT_ARRAY || 25 | column.Type == meta.FLOAT_ARRAY || 26 | column.Type == meta.STRING_ARRAY || 27 | column.Type == meta.DATE_ARRAY || 28 | column.Type == meta.ENUM_ARRAY || 29 | column.Type == meta.VALUE_OBJECT_ARRAY || 30 | column.Type == meta.ENTITY_ARRAY { 31 | jsonString, err := json.Marshal(value) 32 | if err != nil { 33 | panic(err.Error()) 34 | } 35 | value = jsonString 36 | } else if column.Type == meta.FILE { 37 | file := value.(storage.File) 38 | jsonString, err := json.Marshal(file.Save()) 39 | if err != nil { 40 | panic(err.Error()) 41 | } 42 | value = jsonString 43 | } 44 | objValues = append(objValues, value) 45 | } 46 | return objValues 47 | } 48 | 49 | func makeInterfaceQueryValues(intf *graph.Interface) []interface{} { 50 | names := intf.AllAttributeNames() 51 | values := make([]interface{}, len(names)) 52 | for i, attrName := range names { 53 | attr := intf.GetAttributeByName(attrName) 54 | values[i] = makeAttributeValue(attr) 55 | } 56 | 57 | return values 58 | } 59 | 60 | func makeEntityQueryValues(ent *graph.Entity) []interface{} { 61 | names := ent.AllAttributeNames() 62 | values := make([]interface{}, len(names)) 63 | for i, attrName := range names { 64 | attr := ent.GetAttributeByName(attrName) 65 | values[i] = makeAttributeValue(attr) 66 | } 67 | 68 | return values 69 | } 70 | 71 | func makeAttributeValue(attr *graph.Attribute) interface{} { 72 | switch attr.Type { 73 | case meta.ID: 74 | var value db.NullUint64 75 | return &value 76 | case meta.INT: 77 | var value sql.NullInt64 78 | return &value 79 | case meta.FLOAT: 80 | var value sql.NullFloat64 81 | return &value 82 | case meta.BOOLEAN: 83 | var value sql.NullBool 84 | return &value 85 | case meta.DATE: 86 | var value sql.NullTime 87 | return &value 88 | case meta.CLASS_VALUE_OBJECT, 89 | meta.ID_ARRAY, 90 | meta.INT_ARRAY, 91 | meta.FLOAT_ARRAY, 92 | meta.STRING_ARRAY, 93 | meta.DATE_ARRAY, 94 | meta.ENUM_ARRAY, 95 | meta.VALUE_OBJECT_ARRAY, 96 | meta.ENTITY_ARRAY, 97 | meta.FILE: 98 | var value utils.JSON 99 | return &value 100 | // COLUMN_SIMPLE_ARRAY string = "simpleArray" ##待添加代码 101 | // COLUMN_JSON_ARRAY string = "JsonArray" 102 | default: 103 | var value sql.NullString 104 | return &value 105 | } 106 | } 107 | 108 | func convertValuesToInterface(values []interface{}, intf *graph.Interface) map[string]interface{} { 109 | object := make(map[string]interface{}) 110 | names := intf.AllAttributeNames() 111 | for i := range names { 112 | value := values[i] 113 | attrName := names[i] 114 | column := intf.GetAttributeByName(attrName) 115 | object[column.Name] = convertOneColumnValue(column, value) 116 | 117 | } 118 | return object 119 | } 120 | 121 | func convertValuesToEntity(values []interface{}, ent *graph.Entity) map[string]interface{} { 122 | object := make(map[string]interface{}) 123 | names := ent.AllAttributeNames() 124 | for i := range names { 125 | value := values[i] 126 | attrName := names[i] 127 | column := ent.GetAttributeByName(attrName) 128 | object[column.Name] = convertOneColumnValue(column, value) 129 | } 130 | return object 131 | } 132 | 133 | func convertOneColumnValue(column *graph.Attribute, value interface{}) interface{} { 134 | switch column.Type { 135 | case meta.ID, meta.INT: 136 | nullValue := value.(*db.NullUint64) 137 | if nullValue.Valid { 138 | return nullValue.Uint64 139 | } 140 | case meta.FLOAT: 141 | nullValue := value.(*sql.NullFloat64) 142 | if nullValue.Valid { 143 | return nullValue.Float64 144 | } 145 | case meta.BOOLEAN: 146 | nullValue := value.(*sql.NullBool) 147 | if nullValue.Valid { 148 | return nullValue.Bool 149 | } 150 | case meta.DATE: 151 | nullValue := value.(*sql.NullTime) 152 | if nullValue.Valid { 153 | return nullValue.Time 154 | } 155 | case meta.VALUE_OBJECT, 156 | meta.ID_ARRAY, 157 | meta.INT_ARRAY, 158 | meta.FLOAT_ARRAY, 159 | meta.STRING_ARRAY, 160 | meta.DATE_ARRAY, 161 | meta.ENUM_ARRAY, 162 | meta.VALUE_OBJECT_ARRAY, 163 | meta.ENTITY_ARRAY: 164 | if value != nil { 165 | return *value.(*utils.JSON) 166 | } 167 | return value 168 | case meta.FILE: 169 | var file storage.FileInfo 170 | if value != nil { 171 | err := mapstructure.Decode(value, &file) 172 | if err != nil { 173 | panic(err.Error()) 174 | } 175 | return file 176 | } 177 | default: 178 | nullValue := value.(*sql.NullString) 179 | if nullValue.Valid { 180 | return nullValue.String 181 | } 182 | } 183 | 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /repository/db.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/config" 7 | ) 8 | 9 | func DbString(cfg config.DbConfig) string { 10 | return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", 11 | cfg.User, 12 | cfg.Password, 13 | cfg.Host, 14 | cfg.Port, 15 | cfg.Database, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /repository/metas.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/mitchellh/mapstructure" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model" 7 | "rxdrag.com/entify/model/graph" 8 | "rxdrag.com/entify/model/meta" 9 | "rxdrag.com/entify/utils" 10 | ) 11 | 12 | func QueryPublishedMeta() interface{} { 13 | publishedMeta := QueryOneEntity(model.GlobalModel.Graph.GetMetaEntity(), graph.QueryArg{ 14 | consts.ARG_WHERE: graph.QueryArg{ 15 | consts.META_STATUS: graph.QueryArg{ 16 | consts.ARG_EQ: meta.META_STATUS_PUBLISHED, 17 | }, 18 | }, 19 | }, NewSupperVerifier()) 20 | 21 | return publishedMeta 22 | } 23 | 24 | func QueryNextMeta() interface{} { 25 | nextMeta := QueryOneEntity(model.GlobalModel.Graph.GetMetaEntity(), graph.QueryArg{ 26 | consts.ARG_WHERE: graph.QueryArg{ 27 | consts.META_STATUS: graph.QueryArg{ 28 | consts.ARG_ISNULL: true, 29 | }, 30 | }, 31 | }, NewSupperVerifier()) 32 | return nextMeta 33 | } 34 | 35 | func DecodeContent(obj interface{}) *meta.MetaContent { 36 | content := meta.MetaContent{} 37 | if obj != nil { 38 | err := mapstructure.Decode(obj.(utils.Object)[consts.META_CONTENT], &content) 39 | if err != nil { 40 | panic("Decode content failure:" + err.Error()) 41 | } 42 | } 43 | return &content 44 | } 45 | 46 | func InitGlobalModel() { 47 | //初始值,用户取meta信息,取完后,换掉该部分内容 48 | initMeta := meta.MetaContent{ 49 | Classes: []meta.ClassMeta{ 50 | meta.MetaStatusEnum, 51 | meta.MetaClass, 52 | }, 53 | } 54 | model.GlobalModel = model.New(&initMeta) 55 | } 56 | 57 | func LoadModel() { 58 | publishedMeta := QueryPublishedMeta() 59 | publishedContent := DecodeContent(publishedMeta) 60 | publishedContent.Classes = append(publishedContent.Classes, meta.MetaStatusEnum) 61 | publishedContent.Classes = append(publishedContent.Classes, meta.MetaClass) 62 | 63 | model.GlobalModel = model.New(publishedContent) 64 | } 65 | 66 | // func init() { 67 | // LoadModel() 68 | // } 69 | -------------------------------------------------------------------------------- /repository/migrate.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "rxdrag.com/entify/db" 8 | "rxdrag.com/entify/db/dialect" 9 | "rxdrag.com/entify/model" 10 | "rxdrag.com/entify/model/table" 11 | ) 12 | 13 | func ExcuteDiff(d *model.Diff) { 14 | var undoList []string 15 | con, err := Open(NewSupperVerifier()) 16 | dbx := con.Dbx 17 | if err != nil { 18 | panic("Open db error:" + err.Error()) 19 | } 20 | 21 | for _, table := range d.DeletedTables { 22 | err = DeleteTable(table, &undoList, dbx) 23 | if err != nil { 24 | rollback(undoList, dbx) 25 | panic("Delete table error:" + err.Error()) 26 | } 27 | } 28 | 29 | for _, table := range d.AddedTables { 30 | err = CreateTable(table, &undoList, dbx) 31 | if err != nil { 32 | rollback(undoList, dbx) 33 | panic("Create table error:" + err.Error()) 34 | } 35 | } 36 | 37 | for _, tableDiff := range d.ModifiedTables { 38 | err = ModifyTable(tableDiff, &undoList, dbx) 39 | if err != nil { 40 | rollback(undoList, dbx) 41 | panic("Modify table error:" + err.Error()) 42 | } 43 | } 44 | } 45 | 46 | func DeleteTable(table *table.Table, undoList *[]string, dbx *db.Dbx) error { 47 | sqlBuilder := dialect.GetSQLBuilder() 48 | excuteSQL := sqlBuilder.BuildDeleteTableSQL(table) 49 | undoSQL := sqlBuilder.BuildCreateTableSQL(table) 50 | _, err := dbx.Exec(excuteSQL) 51 | if err != nil { 52 | return err 53 | } 54 | *undoList = append(*undoList, undoSQL) 55 | log.Println("Delete Table SQL:", excuteSQL) 56 | return nil 57 | } 58 | 59 | func CreateTable(table *table.Table, undoList *[]string, dbx *db.Dbx) error { 60 | sqlBuilder := dialect.GetSQLBuilder() 61 | excuteSQL := sqlBuilder.BuildCreateTableSQL(table) 62 | undoSQL := sqlBuilder.BuildDeleteTableSQL(table) 63 | _, err := dbx.Exec(excuteSQL) 64 | if err != nil { 65 | return err 66 | } 67 | *undoList = append(*undoList, undoSQL) 68 | log.Println("Add Table SQL:", excuteSQL) 69 | 70 | return nil 71 | } 72 | 73 | func ModifyTable(tableDiff *model.TableDiff, undoList *[]string, dbx *db.Dbx) error { 74 | sqlBuilder := dialect.GetSQLBuilder() 75 | atoms := sqlBuilder.BuildModifyTableAtoms(tableDiff) 76 | for _, atom := range atoms { 77 | _, err := dbx.Exec(atom.ExcuteSQL) 78 | if err != nil { 79 | fmt.Println("Error atom", atom.ExcuteSQL, err.Error()) 80 | return err 81 | } 82 | *undoList = append(*undoList, atom.UndoSQL) 83 | } 84 | return nil 85 | } 86 | 87 | func rollback(undoList []string, con *db.Dbx) { 88 | for _, sql := range undoList { 89 | _, err := con.Exec(sql) 90 | if err != nil { 91 | log.Println("Rollback failed:", sql) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /repository/repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/db/dialect" 7 | "rxdrag.com/entify/model/data" 8 | "rxdrag.com/entify/model/graph" 9 | ) 10 | 11 | func QueryInterface(intf *graph.Interface, args graph.QueryArg, v *AbilityVerifier) []InsanceData { 12 | con, err := Open(v) 13 | if err != nil { 14 | panic(err.Error()) 15 | } 16 | return con.doQueryInterface(intf, args) 17 | } 18 | 19 | func QueryOneInterface(intf *graph.Interface, args graph.QueryArg, v *AbilityVerifier) interface{} { 20 | con, err := Open(v) 21 | if err != nil { 22 | panic(err.Error()) 23 | } 24 | return con.doQueryOneInterface(intf, args) 25 | } 26 | 27 | func QueryEntity(entity *graph.Entity, args graph.QueryArg, v *AbilityVerifier) []InsanceData { 28 | con, err := Open(v) 29 | if err != nil { 30 | panic(err.Error()) 31 | } 32 | return con.doQueryEntity(entity, args) 33 | } 34 | 35 | func QueryOneEntity(entity *graph.Entity, args graph.QueryArg, v *AbilityVerifier) interface{} { 36 | con, err := Open(v) 37 | if err != nil { 38 | panic(err.Error()) 39 | } 40 | return con.doQueryOneEntity(entity, args) 41 | } 42 | 43 | func SaveOne(instance *data.Instance, v *AbilityVerifier) (interface{}, error) { 44 | con, err := Open(v) 45 | if err != nil { 46 | fmt.Println(err.Error()) 47 | return nil, err 48 | } 49 | err = con.BeginTx() 50 | defer con.ClearTx() 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | return nil, err 54 | } 55 | 56 | obj, err := con.doSaveOne(instance) 57 | if err != nil { 58 | fmt.Println(err.Error()) 59 | return nil, err 60 | } 61 | err = con.Commit() 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | return nil, err 65 | } 66 | return obj, nil 67 | } 68 | 69 | func InsertOne(instance *data.Instance, v *AbilityVerifier) (interface{}, error) { 70 | con, err := Open(v) 71 | if err != nil { 72 | fmt.Println(err.Error()) 73 | return nil, err 74 | } 75 | defer con.ClearTx() 76 | if err != nil { 77 | fmt.Println(err.Error()) 78 | return nil, err 79 | } 80 | 81 | obj, err := con.doInsertOne(instance) 82 | if err != nil { 83 | fmt.Println(err.Error()) 84 | return nil, err 85 | } 86 | err = con.Commit() 87 | if err != nil { 88 | fmt.Println(err.Error()) 89 | return nil, err 90 | } 91 | return obj, nil 92 | } 93 | 94 | func BatchQueryAssociations( 95 | association *graph.Association, 96 | ids []uint64, 97 | args graph.QueryArg, 98 | v *AbilityVerifier, 99 | ) []map[string]interface{} { 100 | con, err := Open(v) 101 | if err != nil { 102 | panic(err.Error()) 103 | } 104 | if association.IsAbstract() { 105 | return con.doBatchAbstractRealAssociations(association, ids, args, v) 106 | } else { 107 | return con.doBatchRealAssociations(association, ids, args, v) 108 | } 109 | } 110 | 111 | func IsEntityExists(name string) bool { 112 | con, err := Open(NewSupperVerifier()) 113 | if err != nil { 114 | panic(err.Error()) 115 | } 116 | return con.doCheckEntity(name) 117 | } 118 | 119 | func Install() error { 120 | sqlBuilder := dialect.GetSQLBuilder() 121 | con, err := Open(NewSupperVerifier()) 122 | if err != nil { 123 | fmt.Println(err.Error()) 124 | return err 125 | } 126 | err = con.BeginTx() 127 | if err != nil { 128 | fmt.Println(err.Error()) 129 | return err 130 | } 131 | 132 | _, err = con.Dbx.Exec(sqlBuilder.BuildCreateMetaSQL()) 133 | if err != nil { 134 | fmt.Println(err.Error()) 135 | return err 136 | } 137 | 138 | _, err = con.Dbx.Exec(sqlBuilder.BuildCreateAbilitySQL()) 139 | if err != nil { 140 | fmt.Println(err.Error()) 141 | return err 142 | } 143 | 144 | _, err = con.Dbx.Exec(sqlBuilder.BuildCreateEntityAuthSettingsSQL()) 145 | if err != nil { 146 | fmt.Println(err.Error()) 147 | return err 148 | } 149 | 150 | err = con.Commit() 151 | 152 | if err != nil { 153 | fmt.Println(err.Error()) 154 | return err 155 | } 156 | 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /resolve/convertid.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "strconv" 5 | 6 | "rxdrag.com/entify/consts" 7 | ) 8 | 9 | func ConvertId(object map[string]interface{}) map[string]interface{} { 10 | if object[consts.ID] == nil { 11 | return object 12 | } 13 | switch object[consts.ID].(type) { 14 | case string: 15 | id, err := strconv.ParseUint(object[consts.ID].(string), 10, 64) 16 | if err != nil { 17 | panic("Convert id error:" + err.Error()) 18 | } 19 | 20 | object[consts.ID] = id 21 | } 22 | 23 | return object 24 | } 25 | -------------------------------------------------------------------------------- /resolve/file.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/config" 6 | "rxdrag.com/entify/consts" 7 | "rxdrag.com/entify/storage" 8 | "rxdrag.com/entify/utils" 9 | ) 10 | 11 | func FileUrlResolve(p graphql.ResolveParams) (interface{}, error) { 12 | defer utils.PrintErrorStack() 13 | if p.Source != nil { 14 | fileInfo := p.Source.(storage.FileInfo) 15 | if config.Storage() == consts.LOCAL { 16 | return p.Context.Value(consts.HOST).(string) + consts.UPLOAD_PRIFIX + "/" + fileInfo.Path, nil 17 | } else { 18 | return fileInfo.Path, nil 19 | } 20 | } 21 | return nil, nil 22 | } 23 | -------------------------------------------------------------------------------- /resolve/loaders.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/graph-gophers/dataloader" 8 | "github.com/graphql-go/graphql" 9 | "rxdrag.com/entify/consts" 10 | "rxdrag.com/entify/model/graph" 11 | "rxdrag.com/entify/repository" 12 | "rxdrag.com/entify/utils" 13 | ) 14 | 15 | type ResolverKey struct { 16 | id uint64 17 | } 18 | 19 | func NewKey(id uint64) *ResolverKey { 20 | return &ResolverKey{ 21 | id: id, 22 | } 23 | } 24 | 25 | func (rk *ResolverKey) String() string { 26 | return fmt.Sprintf("%d", rk.id) 27 | } 28 | 29 | func (rk *ResolverKey) Raw() interface{} { 30 | return rk.id 31 | } 32 | 33 | type Loaders struct { 34 | loaders map[string]*dataloader.Loader 35 | } 36 | 37 | func CreateDataLoaders() *Loaders { 38 | return &Loaders{ 39 | loaders: make(map[string]*dataloader.Loader, 1), 40 | } 41 | } 42 | 43 | func (l *Loaders) GetLoader(p graphql.ResolveParams, association *graph.Association, args graph.QueryArg) *dataloader.Loader { 44 | if l.loaders[association.Path()] == nil { 45 | l.loaders[association.Path()] = dataloader.NewBatchedLoader(QueryBatchFn(p, association, args)) 46 | } 47 | return l.loaders[association.Path()] 48 | } 49 | 50 | func QueryBatchFn(p graphql.ResolveParams, association *graph.Association, args graph.QueryArg) dataloader.BatchFunc { 51 | return func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result { 52 | defer utils.PrintErrorStack() 53 | v := makeAssociAbilityVerifier(p, association) 54 | results := make([]*dataloader.Result, len(keys)) 55 | ids := make([]uint64, len(keys)) 56 | for i := range ids { 57 | ids[i] = keys[i].Raw().(uint64) 58 | } 59 | instances := repository.BatchQueryAssociations(association, ids, args, v) 60 | 61 | for i := range results { 62 | var data interface{} 63 | associationInstances := findInstanceFromArray(ids[i], instances) 64 | if !association.IsArray() { 65 | ln := len(associationInstances) 66 | if ln > 1 { 67 | panic(fmt.Sprintf("To many values for %s : %d", association.Owner().Domain.Name+"."+association.Name(), len(associationInstances))) 68 | } else if ln == 1 { 69 | data = associationInstances[0] 70 | } else { 71 | data = nil 72 | } 73 | } else { 74 | data = associationInstances 75 | } 76 | results[i] = &dataloader.Result{ 77 | Data: data, 78 | } 79 | } 80 | return results 81 | } 82 | } 83 | 84 | func findInstanceFromArray(id uint64, array []map[string]interface{}) []interface{} { 85 | var instances []interface{} 86 | for i, obj := range array { 87 | if obj[consts.ASSOCIATION_OWNER_ID] == id { 88 | instances = append(instances, array[i]) 89 | } 90 | } 91 | return instances 92 | } 93 | -------------------------------------------------------------------------------- /resolve/logout.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/authcontext" 6 | "rxdrag.com/entify/authentication" 7 | "rxdrag.com/entify/utils" 8 | ) 9 | 10 | func Logout(p graphql.ResolveParams) (interface{}, error) { 11 | defer utils.PrintErrorStack() 12 | token := authcontext.ParseContextValues(p).Token 13 | if token != "" { 14 | authentication.Logout(token) 15 | } 16 | return true, nil 17 | } 18 | -------------------------------------------------------------------------------- /resolve/me.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/authcontext" 6 | "rxdrag.com/entify/utils" 7 | ) 8 | 9 | func Me(p graphql.ResolveParams) (interface{}, error) { 10 | defer utils.PrintErrorStack() 11 | return authcontext.ParseContextValues(p).Me, nil 12 | } 13 | -------------------------------------------------------------------------------- /resolve/middleware.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "rxdrag.com/entify/consts" 8 | ) 9 | 10 | func LoadersMiddleware(next http.Handler) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | ctx := context.WithValue(r.Context(), consts.LOADERS, CreateDataLoaders()) 13 | next.ServeHTTP(w, r.WithContext(ctx)) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /resolve/mutation.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model/data" 7 | "rxdrag.com/entify/model/graph" 8 | "rxdrag.com/entify/repository" 9 | "rxdrag.com/entify/utils" 10 | ) 11 | 12 | func PostOneResolveFn(entity *graph.Entity) graphql.FieldResolveFn { 13 | return func(p graphql.ResolveParams) (interface{}, error) { 14 | defer utils.PrintErrorStack() 15 | object := p.Args[consts.ARG_OBJECT].(map[string]interface{}) 16 | ConvertId(object) 17 | v := makeEntityAbilityVerifier(p, entity.Uuid()) 18 | instance := data.NewInstance(object, entity) 19 | return repository.SaveOne(instance, v) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /resolve/publish.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/graphql-go/graphql" 8 | "rxdrag.com/entify/consts" 9 | "rxdrag.com/entify/model" 10 | "rxdrag.com/entify/model/data" 11 | "rxdrag.com/entify/model/meta" 12 | "rxdrag.com/entify/repository" 13 | "rxdrag.com/entify/utils" 14 | ) 15 | 16 | func doPublish(v *repository.AbilityVerifier) error { 17 | publishedMeta := repository.QueryPublishedMeta() 18 | nextMeta := repository.QueryNextMeta() 19 | fmt.Println("Start to publish") 20 | // fmt.Println("Published Meta ID:", publishedMeta.(utils.Object)["id"]) 21 | // fmt.Println("Next Meta ID:", nextMeta.(utils.Object)["id"]) 22 | 23 | if nextMeta == nil { 24 | panic("Can not find unpublished meta") 25 | } 26 | publishedModel := model.New(repository.DecodeContent(publishedMeta)) 27 | nextModel := model.New(repository.DecodeContent(nextMeta)) 28 | nextModel.Graph.Validate() 29 | diff := model.CreateDiff(publishedModel, nextModel) 30 | repository.ExcuteDiff(diff) 31 | fmt.Println("ExcuteDiff success") 32 | metaObj := nextMeta.(utils.Object) 33 | metaObj[consts.META_STATUS] = meta.META_STATUS_PUBLISHED 34 | metaObj[consts.META_PUBLISHEDAT] = time.Now() 35 | _, err := repository.SaveOne(data.NewInstance(metaObj, model.GlobalModel.Graph.GetMetaEntity()), v) 36 | if err != nil { 37 | return err 38 | } 39 | //repository.LoadModel() 40 | 41 | return nil 42 | } 43 | 44 | func PublishMetaResolve(p graphql.ResolveParams) (interface{}, error) { 45 | defer utils.PrintErrorStack() 46 | v := makeEntityAbilityVerifier(p, meta.META_ENTITY_UUID) 47 | doPublish(v) 48 | return "success", nil 49 | } 50 | 51 | func SyncMetaResolve(p graphql.ResolveParams) (interface{}, error) { 52 | object := p.Args[consts.ARG_OBJECT].(map[string]interface{}) 53 | v := makeEntityAbilityVerifier(p, meta.META_ENTITY_UUID) 54 | return repository.InsertOne(data.NewInstance(object, model.GlobalModel.Graph.GetMetaEntity()), v) 55 | } 56 | -------------------------------------------------------------------------------- /resolve/query.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/graphql-go/graphql" 7 | "rxdrag.com/entify/consts" 8 | "rxdrag.com/entify/model/graph" 9 | "rxdrag.com/entify/repository" 10 | "rxdrag.com/entify/utils" 11 | ) 12 | 13 | func QueryOneInterfaceResolveFn(intf *graph.Interface) graphql.FieldResolveFn { 14 | return func(p graphql.ResolveParams) (interface{}, error) { 15 | defer utils.PrintErrorStack() 16 | v := makeInterfaceAbilityVerifier(p, intf) 17 | instance := repository.QueryOneInterface(intf, p.Args, v) 18 | return instance, nil 19 | } 20 | } 21 | 22 | func QueryInterfaceResolveFn(intf *graph.Interface) graphql.FieldResolveFn { 23 | return func(p graphql.ResolveParams) (interface{}, error) { 24 | defer utils.PrintErrorStack() 25 | v := makeInterfaceAbilityVerifier(p, intf) 26 | return repository.QueryInterface(intf, p.Args, v), nil 27 | } 28 | } 29 | 30 | func QueryOneEntityResolveFn(entity *graph.Entity) graphql.FieldResolveFn { 31 | return func(p graphql.ResolveParams) (interface{}, error) { 32 | defer utils.PrintErrorStack() 33 | v := makeEntityAbilityVerifier(p, entity.Uuid()) 34 | instance := repository.QueryOneEntity(entity, p.Args, v) 35 | return instance, nil 36 | } 37 | } 38 | 39 | func QueryEntityResolveFn(entity *graph.Entity) graphql.FieldResolveFn { 40 | return func(p graphql.ResolveParams) (interface{}, error) { 41 | defer utils.PrintErrorStack() 42 | // for _, iSelection := range p.Info.Operation.GetSelectionSet().Selections { 43 | // switch selection := iSelection.(type) { 44 | // case *ast.Field: 45 | // //fmt.Println(selection.Directives[len(selection.Directives)-1].Name.Value) 46 | // case *ast.InlineFragment: 47 | // case *ast.FragmentSpread: 48 | // } 49 | // } 50 | v := makeEntityAbilityVerifier(p, entity.Uuid()) 51 | return repository.QueryEntity(entity, p.Args, v), nil 52 | } 53 | } 54 | 55 | func QueryAssociationFn(asso *graph.Association) graphql.FieldResolveFn { 56 | return func(p graphql.ResolveParams) (interface{}, error) { 57 | var ( 58 | source = p.Source.(map[string]interface{}) 59 | v = p.Context.Value 60 | loaders = v(consts.LOADERS).(*Loaders) 61 | handleError = func(err error) error { 62 | return fmt.Errorf(err.Error()) 63 | } 64 | ) 65 | defer utils.PrintErrorStack() 66 | 67 | if loaders == nil { 68 | panic("Data loaders is nil") 69 | } 70 | loader := loaders.GetLoader(p, asso, p.Args) 71 | thunk := loader.Load(p.Context, NewKey(source[consts.ID].(uint64))) 72 | return func() (interface{}, error) { 73 | data, err := thunk() 74 | if err != nil { 75 | return nil, handleError(err) 76 | } 77 | 78 | var retValue interface{} 79 | if data == nil { 80 | if asso.IsArray() { 81 | retValue = []map[string]interface{}{} 82 | } else { 83 | retValue = nil 84 | } 85 | } else { 86 | retValue = data 87 | } 88 | return retValue, nil 89 | }, nil 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /resolve/verifier.go: -------------------------------------------------------------------------------- 1 | package resolve 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/graph" 6 | "rxdrag.com/entify/repository" 7 | ) 8 | 9 | func makeEntityAbilityVerifier(p graphql.ResolveParams, entityUuid string) *repository.AbilityVerifier { 10 | verifier := repository.NewVerifier() 11 | 12 | verifier.Init(p, []string{entityUuid}) 13 | 14 | // if !verifier.CanReadEntity() && !node.IsInterface() { 15 | // panic("No permission to read: " + node.Name()) 16 | // } 17 | 18 | // args := verifier.WeaveAuthInArgs(inputArgs) 19 | return verifier 20 | } 21 | 22 | func makeInterfaceAbilityVerifier(p graphql.ResolveParams, intf *graph.Interface) *repository.AbilityVerifier { 23 | verifier := repository.NewVerifier() 24 | var uuids []string 25 | for i := range intf.Children { 26 | uuids = append(uuids, intf.Children[i].Uuid()) 27 | } 28 | verifier.Init(p, uuids) 29 | return verifier 30 | } 31 | 32 | func makeAssociAbilityVerifier(p graphql.ResolveParams, association *graph.Association) *repository.AbilityVerifier { 33 | verifier := repository.NewVerifier() 34 | typeInterface := association.TypeInterface() 35 | if typeInterface != nil { 36 | var uuids []string 37 | for i := range typeInterface.Children { 38 | uuids = append(uuids, typeInterface.Children[i].Uuid()) 39 | } 40 | verifier.Init(p, uuids) 41 | } else { 42 | verifier.Init(p, []string{association.TypeClass().Uuid()}) 43 | } 44 | return verifier 45 | } 46 | -------------------------------------------------------------------------------- /scalars/federation.go: -------------------------------------------------------------------------------- 1 | package scalars 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/consts" 6 | ) 7 | 8 | var AnyType = graphql.NewScalar( 9 | graphql.ScalarConfig{ 10 | Name: consts.SCALAR_ANY, 11 | Description: "The `_Any` scalar type represents _Any values as specified by [Federation](https://www.apollographql.com/docs/federation/federation-spec)", 12 | Serialize: func(value interface{}) interface{} { 13 | return value 14 | }, 15 | ParseValue: func(value interface{}) interface{} { 16 | return value 17 | }, 18 | ParseLiteral: parseLiteral, 19 | }, 20 | ) 21 | 22 | var FieldSetType = graphql.NewScalar( 23 | graphql.ScalarConfig{ 24 | Name: consts.SCALAR_FIELDSET, 25 | Description: "The `_FieldSet` scalar type represents _FieldSet values as specified by [Federation](https://www.apollographql.com/docs/federation/federation-spec)", 26 | Serialize: func(value interface{}) interface{} { 27 | return value 28 | }, 29 | ParseValue: func(value interface{}) interface{} { 30 | return value 31 | }, 32 | ParseLiteral: parseLiteral, 33 | }, 34 | ) 35 | -------------------------------------------------------------------------------- /scalars/json.go: -------------------------------------------------------------------------------- 1 | package scalars 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "github.com/graphql-go/graphql/language/ast" 6 | "github.com/graphql-go/graphql/language/kinds" 7 | ) 8 | 9 | func parseLiteral(astValue ast.Value) interface{} { 10 | kind := astValue.GetKind() 11 | 12 | switch kind { 13 | case kinds.StringValue: 14 | return astValue.GetValue() 15 | case kinds.BooleanValue: 16 | return astValue.GetValue() 17 | case kinds.IntValue: 18 | return astValue.GetValue() 19 | case kinds.FloatValue: 20 | return astValue.GetValue() 21 | case kinds.ObjectValue: 22 | obj := make(map[string]interface{}) 23 | for _, v := range astValue.GetValue().([]*ast.ObjectField) { 24 | obj[v.Name.Value] = parseLiteral(v.Value) 25 | } 26 | return obj 27 | case kinds.ListValue: 28 | list := make([]interface{}, 0) 29 | for _, v := range astValue.GetValue().([]ast.Value) { 30 | list = append(list, parseLiteral(v)) 31 | } 32 | return list 33 | default: 34 | return nil 35 | } 36 | } 37 | 38 | // JSON type 39 | var JSONType = graphql.NewScalar( 40 | graphql.ScalarConfig{ 41 | Name: "JSON", 42 | Description: "The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)", 43 | Serialize: func(value interface{}) interface{} { 44 | return value 45 | }, 46 | ParseValue: func(value interface{}) interface{} { 47 | return value 48 | }, 49 | ParseLiteral: parseLiteral, 50 | }, 51 | ) 52 | -------------------------------------------------------------------------------- /scalars/upload.go: -------------------------------------------------------------------------------- 1 | package scalars 2 | 3 | import "github.com/graphql-go/graphql" 4 | 5 | // Upload type 6 | var UploadType = graphql.NewScalar( 7 | graphql.ScalarConfig{ 8 | Name: "Upload", 9 | Description: "The `Upload` scalar type ", 10 | Serialize: func(value interface{}) interface{} { 11 | return value 12 | }, 13 | ParseValue: func(value interface{}) interface{} { 14 | return value 15 | }, 16 | ParseLiteral: parseLiteral, 17 | }, 18 | ) 19 | -------------------------------------------------------------------------------- /schema/cache.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model" 6 | "rxdrag.com/entify/model/graph" 7 | ) 8 | 9 | type TypeCache struct { 10 | ObjectTypeMap map[string]*graphql.Object 11 | ObjectMapById map[uint64]*graphql.Object 12 | EnumTypeMap map[string]*graphql.Enum 13 | InterfaceTypeMap map[string]*graphql.Interface 14 | SetInputMap map[string]*graphql.InputObject 15 | SaveInputMap map[string]*graphql.InputObject 16 | HasManyInputMap map[string]*graphql.InputObject 17 | HasOneInputMap map[string]*graphql.InputObject 18 | WhereExpMap map[string]*graphql.InputObject 19 | DistinctOnEnumMap map[string]*graphql.Enum 20 | OrderByMap map[string]*graphql.InputObject 21 | EnumComparisonExpMap map[string]*graphql.InputObjectFieldConfig 22 | MutationResponseMap map[string]*graphql.Object 23 | AggregateMap map[string]*graphql.Object 24 | SelectColumnsMap map[string]*graphql.InputObject 25 | } 26 | 27 | func (c *TypeCache) MakeCache() { 28 | c.clearCache() 29 | c.makeEnums(model.GlobalModel.Graph.Enums) 30 | c.makeOutputInterfaces(model.GlobalModel.Graph.Interfaces) 31 | c.makeEntityOutputObjects(model.GlobalModel.Graph.Entities) 32 | c.makePartialOutputObjects(model.GlobalModel.Graph.Partials) 33 | c.makeExternalOutputObjects(model.GlobalModel.Graph.Externals) 34 | c.makeQueryArgs() 35 | c.makeRelations() 36 | c.makeInputs() 37 | } 38 | 39 | // func (c *TypeCache) OutputInterfaceType(entity *model.Entity) graphql.Type { 40 | // return c.InterfaceTypeMap[entity.Name] 41 | // } 42 | 43 | func (c *TypeCache) InterfaceOutputType(name string) *graphql.Interface { 44 | intf := c.InterfaceTypeMap[name] 45 | if intf != nil { 46 | return intf 47 | } 48 | panic("Can not find interface output type of " + name) 49 | } 50 | 51 | func (c *TypeCache) EntityeOutputType(name string) *graphql.Object { 52 | obj := c.ObjectTypeMap[name] 53 | if obj == nil { 54 | panic("Can not find output type of " + name) 55 | } 56 | return obj 57 | } 58 | 59 | func (c *TypeCache) OutputType(name string) graphql.Type { 60 | intf := c.InterfaceTypeMap[name] 61 | if intf != nil { 62 | return intf 63 | } 64 | obj := c.ObjectTypeMap[name] 65 | if obj == nil { 66 | panic("Can not find output type of " + name) 67 | } 68 | return obj 69 | } 70 | 71 | func (c *TypeCache) GetEntityTypeByInnerId(id uint64) *graphql.Object { 72 | return c.ObjectMapById[id] 73 | } 74 | 75 | func (c *TypeCache) EntityTypes() []graphql.Type { 76 | objs := []graphql.Type{} 77 | for key := range c.ObjectTypeMap { 78 | objs = append(objs, c.ObjectTypeMap[key]) 79 | } 80 | 81 | return objs 82 | } 83 | 84 | func (c *TypeCache) EntityObjects() []*graphql.Object { 85 | objs := []*graphql.Object{} 86 | for key := range c.ObjectTypeMap { 87 | objs = append(objs, c.ObjectTypeMap[key]) 88 | } 89 | 90 | return objs 91 | } 92 | 93 | func (c *TypeCache) EnumType(name string) *graphql.Enum { 94 | return c.EnumTypeMap[name] 95 | } 96 | 97 | func (c *TypeCache) WhereExp(name string) *graphql.InputObject { 98 | return c.WhereExpMap[name] 99 | } 100 | 101 | func (c *TypeCache) OrderByExp(name string) *graphql.InputObject { 102 | return c.OrderByMap[name] 103 | } 104 | 105 | func (c *TypeCache) DistinctOnEnum(name string) *graphql.Enum { 106 | return c.DistinctOnEnumMap[name] 107 | } 108 | 109 | func (c *TypeCache) DistinctOnEnums() map[string]*graphql.Enum { 110 | return c.DistinctOnEnumMap 111 | } 112 | 113 | func (c *TypeCache) SaveInput(name string) *graphql.InputObject { 114 | return c.SaveInputMap[name] 115 | } 116 | 117 | func (c *TypeCache) SetInput(name string) *graphql.InputObject { 118 | return c.SetInputMap[name] 119 | } 120 | func (c *TypeCache) HasManyInput(name string) *graphql.InputObject { 121 | return c.HasManyInputMap[name] 122 | } 123 | func (c *TypeCache) HasOneInput(name string) *graphql.InputObject { 124 | return c.HasOneInputMap[name] 125 | } 126 | 127 | func (c *TypeCache) MutationResponse(name string) *graphql.Object { 128 | return c.MutationResponseMap[name] 129 | } 130 | 131 | func (c *TypeCache) mapInterfaces(entities []*graph.Interface) []*graphql.Interface { 132 | interfaces := []*graphql.Interface{} 133 | for i := range entities { 134 | interfaces = append(interfaces, c.InterfaceTypeMap[entities[i].Domain.Name]) 135 | } 136 | 137 | return interfaces 138 | } 139 | 140 | func (c *TypeCache) clearCache() { 141 | c.ObjectTypeMap = make(map[string]*graphql.Object) 142 | c.ObjectMapById = make(map[uint64]*graphql.Object) 143 | c.EnumTypeMap = make(map[string]*graphql.Enum) 144 | c.InterfaceTypeMap = make(map[string]*graphql.Interface) 145 | c.SetInputMap = make(map[string]*graphql.InputObject) 146 | c.SaveInputMap = make(map[string]*graphql.InputObject) 147 | c.HasManyInputMap = make(map[string]*graphql.InputObject) 148 | c.HasOneInputMap = make(map[string]*graphql.InputObject) 149 | c.WhereExpMap = make(map[string]*graphql.InputObject) 150 | c.DistinctOnEnumMap = make(map[string]*graphql.Enum) 151 | c.OrderByMap = make(map[string]*graphql.InputObject) 152 | c.EnumComparisonExpMap = make(map[string]*graphql.InputObjectFieldConfig) 153 | c.MutationResponseMap = make(map[string]*graphql.Object) 154 | c.AggregateMap = make(map[string]*graphql.Object) 155 | c.SelectColumnsMap = make(map[string]*graphql.InputObject) 156 | } 157 | -------------------------------------------------------------------------------- /schema/enum.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/graph" 6 | ) 7 | 8 | func (c *TypeCache) makeEnums(enums []*graph.Enum) { 9 | for i := range enums { 10 | enum := enums[i] 11 | c.EnumTypeMap[enum.Name] = EnumType(enum) 12 | } 13 | } 14 | 15 | func EnumType(entity *graph.Enum) *graphql.Enum { 16 | enumValueConfigMap := graphql.EnumValueConfigMap{} 17 | for _, value := range entity.Values { 18 | enumValueConfigMap[value] = &graphql.EnumValueConfig{ 19 | Value: value, 20 | } 21 | } 22 | enum := graphql.NewEnum( 23 | graphql.EnumConfig{ 24 | Name: entity.Name, 25 | Values: enumValueConfigMap, 26 | }, 27 | ) 28 | return enum 29 | } 30 | -------------------------------------------------------------------------------- /schema/federation.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var allSDL = ` 8 | extend schema 9 | @link(url: "https://specs.apollo.dev/federation/v2.0", 10 | import: ["@key"]) 11 | 12 | scalar JSON 13 | scalar DateTime 14 | scalar Upload 15 | 16 | type Query { 17 | %s 18 | } 19 | 20 | type Mutation { 21 | %s 22 | } 23 | %s 24 | ` 25 | 26 | func makeFederationSDL() string { 27 | sdl := allSDL 28 | queryFields, queryTypes := querySDL() 29 | mutationFields, mutationTypes := mutationSDL() 30 | return fmt.Sprintf(sdl, queryFields, mutationFields, queryTypes+mutationTypes) 31 | } 32 | -------------------------------------------------------------------------------- /schema/federationmutation.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/graphql-go/graphql" 7 | "rxdrag.com/entify/config" 8 | "rxdrag.com/entify/consts" 9 | "rxdrag.com/entify/model" 10 | "rxdrag.com/entify/model/graph" 11 | "rxdrag.com/entify/model/meta" 12 | ) 13 | 14 | var mutationFieldSDL = "\t%s(%s) : %s \n" 15 | 16 | func mutationSDL() (string, string) { 17 | queryFields := "" 18 | types := "" 19 | if config.AuthUrl() == "" { 20 | queryFields = queryFields + makeLoginSDL() 21 | } 22 | 23 | for _, entity := range model.GlobalModel.Graph.RootEnities() { 24 | if notSystemEntity(entity) { 25 | queryFields = queryFields + makeEntityMutationSDL(entity) 26 | types = types + objectToSDL(Cache.MutationResponse(entity.Name()), false) 27 | } 28 | } 29 | for _, partial := range model.GlobalModel.Graph.RootPartails() { 30 | queryFields = queryFields + makePartialMutationSDL(partial) 31 | types = types + objectToSDL(Cache.MutationResponse(partial.Name()), false) 32 | } 33 | 34 | // for _, exteneral := range model.GlobalModel.Graph.RootExternals() { 35 | // queryFields = queryFields + makeExteneralSDL(exteneral) 36 | // //types = types + objectToSDL(Cache.EntityeOutputType(exteneral.Name())) 37 | // } 38 | for _, input := range Cache.SetInputMap { 39 | if len(input.Fields()) > 0 && 40 | input.Name() != meta.MetaClass.Name && 41 | input.Name() != meta.MetaClass.Name+consts.SET && 42 | input.Name() != meta.AbilityClass.Name && 43 | input.Name() != meta.EntityAuthSettingsClass.Name { 44 | types = types + inputToSDL(input) 45 | } 46 | 47 | } 48 | for _, input := range Cache.SaveInputMap { 49 | types = types + inputToSDL(input) 50 | } 51 | for _, input := range Cache.HasManyInputMap { 52 | types = types + inputToSDL(input) 53 | } 54 | for _, input := range Cache.HasOneInputMap { 55 | types = types + inputToSDL(input) 56 | } 57 | 58 | return queryFields, types 59 | } 60 | 61 | func makeEntityMutationSDL(entity *graph.Entity) string { 62 | sdl := "" 63 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 64 | entity.DeleteName(), 65 | makeArgsSDL(deleteArgs(entity)), 66 | Cache.MutationResponse(entity.Name()).Name(), 67 | ) 68 | 69 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 70 | entity.DeleteByIdName(), 71 | makeArgsSDL(deleteByIdArgs()), 72 | Cache.OutputType(entity.Name()).String(), 73 | ) 74 | 75 | updateInput := Cache.SetInput(entity.Name()) 76 | if len(updateInput.Fields()) > 0 { 77 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 78 | entity.SetName(), 79 | makeArgsSDL(setArgs(entity)), 80 | Cache.MutationResponse(entity.Name()).String(), 81 | ) 82 | } 83 | 84 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 85 | entity.UpsertName(), 86 | makeArgsSDL(upsertArgs(entity)), 87 | (&graphql.List{OfType: Cache.OutputType(entity.Name())}).String(), 88 | ) 89 | 90 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 91 | entity.UpsertOneName(), 92 | makeArgsSDL(upsertOneArgs(entity)), 93 | Cache.OutputType(entity.Name()).String(), 94 | ) 95 | 96 | return sdl 97 | } 98 | 99 | func makePartialMutationSDL(partial *graph.Partial) string { 100 | sdl := "" 101 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 102 | partial.DeleteName(), 103 | makeArgsSDL(deleteArgs(&partial.Entity)), 104 | Cache.MutationResponse(partial.Name()).Name(), 105 | ) 106 | 107 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 108 | partial.DeleteByIdName(), 109 | makeArgsSDL(deleteByIdArgs()), 110 | Cache.OutputType(partial.Name()).String(), 111 | ) 112 | 113 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 114 | partial.InsertName(), 115 | makeArgsSDL(upsertArgs(&partial.Entity)), 116 | (&graphql.List{OfType: Cache.OutputType(partial.Name())}).String(), 117 | ) 118 | 119 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 120 | partial.InsertOneName(), 121 | makeArgsSDL(upsertOneArgs(&partial.Entity)), 122 | Cache.OutputType(partial.Name()).String(), 123 | ) 124 | 125 | updateInput := Cache.SetInput(partial.Name()) 126 | if len(updateInput.Fields()) > 0 { 127 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 128 | partial.SetName(), 129 | makeArgsSDL(setArgs(&partial.Entity)), 130 | Cache.MutationResponse(partial.Name()).String(), 131 | ) 132 | } 133 | 134 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 135 | partial.UpdateName(), 136 | makeArgsSDL(upsertArgs(&partial.Entity)), 137 | (&graphql.List{OfType: Cache.OutputType(partial.Name())}).String(), 138 | ) 139 | 140 | sdl = sdl + fmt.Sprintf(mutationFieldSDL, 141 | partial.UpdateOneName(), 142 | makeArgsSDL(upsertOneArgs(&partial.Entity)), 143 | Cache.OutputType(partial.Name()).String(), 144 | ) 145 | 146 | return sdl 147 | } 148 | 149 | func makeLoginSDL() string { 150 | return ` login(loginName: String!password: String!): String 151 | logout: Boolean` 152 | } 153 | -------------------------------------------------------------------------------- /schema/globals.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/config" 6 | "rxdrag.com/entify/consts" 7 | "rxdrag.com/entify/resolve" 8 | "rxdrag.com/entify/utils" 9 | ) 10 | 11 | var Cache TypeCache 12 | 13 | var _ServiceType = graphql.NewObject( 14 | graphql.ObjectConfig{ 15 | Name: consts.SERVICE_TYPE, 16 | Fields: graphql.Fields{ 17 | consts.ID: &graphql.Field{ //扩展一个id字段 18 | Type: graphql.Int, 19 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 20 | defer utils.PrintErrorStack() 21 | return config.ServiceId(), nil 22 | }, 23 | }, 24 | 25 | //扩展字段 26 | consts.INSTALLED: &graphql.Field{ 27 | Type: graphql.Boolean, 28 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 29 | defer utils.PrintErrorStack() 30 | return true, nil 31 | }, 32 | }, 33 | 34 | consts.CAN_UPLOAD: &graphql.Field{ 35 | Type: graphql.Boolean, 36 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 37 | defer utils.PrintErrorStack() 38 | return config.Storage() != "", nil 39 | }, 40 | }, 41 | 42 | consts.SDL: &graphql.Field{ 43 | Type: graphql.String, 44 | Description: "Service SDL", 45 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 46 | defer utils.PrintErrorStack() 47 | return makeFederationSDL(), nil 48 | }, 49 | }, 50 | }, 51 | Description: "_Service type of federation schema specification, and extends other fields", 52 | }, 53 | ) 54 | 55 | var EntityType *graphql.Union 56 | 57 | var installInputType = graphql.NewInputObject( 58 | graphql.InputObjectConfig{ 59 | Name: "InstallInput", 60 | Fields: graphql.InputObjectConfigFieldMap{ 61 | consts.ADMIN: &graphql.InputObjectFieldConfig{ 62 | Type: &graphql.NonNull{ 63 | OfType: graphql.String, 64 | }, 65 | }, 66 | consts.ADMINPASSWORD: &graphql.InputObjectFieldConfig{ 67 | Type: &graphql.NonNull{ 68 | OfType: graphql.String, 69 | }, 70 | }, 71 | consts.WITHDEMO: &graphql.InputObjectFieldConfig{ 72 | Type: graphql.Boolean, 73 | }, 74 | }, 75 | }, 76 | ) 77 | 78 | var fileOutputType = graphql.NewObject( 79 | graphql.ObjectConfig{ 80 | Name: consts.FILE, 81 | Fields: graphql.Fields{ 82 | consts.FILE_NAME: &graphql.Field{ 83 | Type: graphql.String, 84 | }, 85 | consts.FILE_SIZE: &graphql.Field{ 86 | Type: graphql.Int, 87 | }, 88 | consts.FILE_MIMETYPE: &graphql.Field{ 89 | Type: graphql.String, 90 | }, 91 | consts.FILE_URL: &graphql.Field{ 92 | Type: graphql.String, 93 | Resolve: resolve.FileUrlResolve, 94 | }, 95 | consts.File_EXTNAME: &graphql.Field{ 96 | Type: graphql.String, 97 | }, 98 | consts.FILE_THMUBNAIL: &graphql.Field{ 99 | Type: graphql.String, 100 | }, 101 | consts.FILE_RESIZE: &graphql.Field{ 102 | Type: graphql.String, 103 | Args: graphql.FieldConfigArgument{ 104 | consts.FILE_WIDTH: &graphql.ArgumentConfig{ 105 | Type: graphql.Int, 106 | }, 107 | consts.FILE_HEIGHT: &graphql.ArgumentConfig{ 108 | Type: graphql.Int, 109 | }, 110 | }, 111 | }, 112 | }, 113 | Description: "File type", 114 | }, 115 | ) 116 | 117 | var baseRoleTye = graphql.NewObject( 118 | graphql.ObjectConfig{ 119 | Name: "BaseRole", 120 | Fields: graphql.Fields{ 121 | consts.ID: &graphql.Field{ 122 | Type: graphql.ID, 123 | }, 124 | consts.NAME: &graphql.Field{ 125 | Type: graphql.String, 126 | }, 127 | }, 128 | Description: "Base role for auth", 129 | }, 130 | ) 131 | 132 | var baseUserType = graphql.NewObject( 133 | graphql.ObjectConfig{ 134 | Name: "BaseUser", 135 | Fields: graphql.Fields{ 136 | consts.ID: &graphql.Field{ 137 | Type: graphql.ID, 138 | }, 139 | consts.NAME: &graphql.Field{ 140 | Type: graphql.String, 141 | }, 142 | consts.LOGIN_NAME: &graphql.Field{ 143 | Type: graphql.String, 144 | }, 145 | consts.IS_SUPPER: &graphql.Field{ 146 | Type: graphql.Boolean, 147 | }, 148 | consts.IS_DEMO: &graphql.Field{ 149 | Type: graphql.Boolean, 150 | }, 151 | "roles": &graphql.Field{ 152 | Type: &graphql.List{ 153 | OfType: baseRoleTye, 154 | }, 155 | }, 156 | }, 157 | Description: "Base user for auth", 158 | }, 159 | ) 160 | -------------------------------------------------------------------------------- /schema/interface.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model/graph" 7 | "rxdrag.com/entify/utils" 8 | ) 9 | 10 | func (c *TypeCache) makeOutputInterfaces(interfaces []*graph.Interface) { 11 | for i := range interfaces { 12 | intf := interfaces[i] 13 | c.InterfaceTypeMap[intf.Name()] = c.InterfaceType(intf) 14 | } 15 | } 16 | 17 | func (c *TypeCache) InterfaceType(intf *graph.Interface) *graphql.Interface { 18 | name := intf.Name() 19 | 20 | return graphql.NewInterface( 21 | graphql.InterfaceConfig{ 22 | Name: name, 23 | Fields: outputFields(intf.AllAttributes(), intf.AllMethods()), 24 | Description: intf.Description(), 25 | ResolveType: resolveTypeFn, 26 | }, 27 | ) 28 | } 29 | 30 | func resolveTypeFn(p graphql.ResolveTypeParams) *graphql.Object { 31 | if value, ok := p.Value.(map[string]interface{}); ok { 32 | if id, ok := value[consts.ID].(uint64); ok { 33 | entityInnerId := utils.DecodeEntityInnerId(id) 34 | return Cache.GetEntityTypeByInnerId(entityInnerId) 35 | } 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /schema/model.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/mitchellh/mapstructure" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model" 7 | "rxdrag.com/entify/model/graph" 8 | "rxdrag.com/entify/model/meta" 9 | 10 | "rxdrag.com/entify/repository" 11 | "rxdrag.com/entify/utils" 12 | ) 13 | 14 | func QueryPublishedMeta() interface{} { 15 | publishedMeta := repository.QueryOneEntity(model.GlobalModel.Graph.GetMetaEntity(), graph.QueryArg{ 16 | consts.ARG_WHERE: graph.QueryArg{ 17 | consts.META_STATUS: graph.QueryArg{ 18 | consts.ARG_EQ: meta.META_STATUS_PUBLISHED, 19 | }, 20 | }, 21 | }, repository.NewSupperVerifier()) 22 | 23 | return publishedMeta 24 | } 25 | 26 | func QueryNextMeta() interface{} { 27 | nextMeta := repository.QueryOneEntity(model.GlobalModel.Graph.GetMetaEntity(), graph.QueryArg{ 28 | consts.ARG_WHERE: graph.QueryArg{ 29 | consts.META_STATUS: graph.QueryArg{ 30 | consts.ARG_ISNULL: true, 31 | }, 32 | }, 33 | }, repository.NewSupperVerifier()) 34 | 35 | return nextMeta 36 | } 37 | 38 | func DecodeContent(obj interface{}) *meta.MetaContent { 39 | content := meta.MetaContent{} 40 | if obj != nil { 41 | err := mapstructure.Decode(obj.(utils.Object)[consts.META_CONTENT], &content) 42 | if err != nil { 43 | panic("Decode content failure:" + err.Error()) 44 | } 45 | } 46 | return &content 47 | } 48 | 49 | func LoadModel() { 50 | //初始值,用户取meta信息,取完后,换掉该部分内容 51 | initMeta := meta.MetaContent{ 52 | Classes: []meta.ClassMeta{ 53 | meta.MetaStatusEnum, 54 | meta.MetaClass, 55 | meta.EntityAuthSettingsClass, 56 | meta.AbilityTypeEnum, 57 | meta.AbilityClass, 58 | }, 59 | } 60 | model.GlobalModel = model.New(&initMeta) 61 | publishedMeta := QueryPublishedMeta() 62 | publishedContent := DecodeContent(publishedMeta) 63 | publishedContent.Classes = append(publishedContent.Classes, 64 | meta.MetaStatusEnum, 65 | meta.MetaClass, 66 | meta.EntityAuthSettingsClass, 67 | meta.AbilityTypeEnum, 68 | meta.AbilityClass, 69 | ) 70 | 71 | model.GlobalModel = model.New(publishedContent) 72 | } 73 | 74 | // func init() { 75 | // LoadModel() 76 | // } 77 | -------------------------------------------------------------------------------- /schema/mutation.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/authentication" 6 | "rxdrag.com/entify/config" 7 | "rxdrag.com/entify/consts" 8 | "rxdrag.com/entify/model" 9 | "rxdrag.com/entify/model/graph" 10 | "rxdrag.com/entify/resolve" 11 | "rxdrag.com/entify/utils" 12 | ) 13 | 14 | const INPUT = "input" 15 | 16 | func appendAuthMutation(fields graphql.Fields) { 17 | fields[consts.LOGIN] = &graphql.Field{ 18 | Type: graphql.String, 19 | Args: graphql.FieldConfigArgument{ 20 | consts.LOGIN_NAME: &graphql.ArgumentConfig{ 21 | Type: &graphql.NonNull{OfType: graphql.String}, 22 | }, 23 | consts.PASSWORD: &graphql.ArgumentConfig{ 24 | Type: &graphql.NonNull{OfType: graphql.String}, 25 | }, 26 | }, 27 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 28 | defer utils.PrintErrorStack() 29 | return authentication.Login(p.Args[consts.LOGIN_NAME].(string), p.Args[consts.PASSWORD].(string)) 30 | }, 31 | } 32 | 33 | fields[consts.LOGOUT] = &graphql.Field{ 34 | Type: graphql.Boolean, 35 | Resolve: resolve.Logout, 36 | } 37 | } 38 | 39 | func rootMutation() *graphql.Object { 40 | metaEntity := model.GlobalModel.Graph.GetMetaEntity() 41 | mutationFields := graphql.Fields{ 42 | consts.PUBLISH: &graphql.Field{ 43 | Type: Cache.OutputType(metaEntity.Name()), 44 | Resolve: publishResolve, 45 | }, 46 | consts.ROLLBACK: &graphql.Field{ 47 | Type: Cache.OutputType(metaEntity.Name()), 48 | Resolve: resolve.SyncMetaResolve, 49 | }, 50 | consts.SYNC_META: &graphql.Field{ 51 | Type: Cache.OutputType(metaEntity.Name()), 52 | Resolve: resolve.SyncMetaResolve, 53 | }, 54 | } 55 | 56 | for _, entity := range model.GlobalModel.Graph.RootEnities() { 57 | if entity.Domain.Root { 58 | appendEntityMutationToFields(entity, mutationFields) 59 | } 60 | } 61 | 62 | for _, partial := range model.GlobalModel.Graph.RootPartails() { 63 | if partial.Domain.Root { 64 | appendPartialMutationToFields(partial, mutationFields) 65 | } 66 | } 67 | 68 | if config.AuthUrl() == "" { 69 | appendAuthMutation(mutationFields) 70 | } 71 | 72 | rootMutation := graphql.ObjectConfig{ 73 | Name: consts.ROOT_MUTATION_NAME, 74 | Fields: mutationFields, 75 | Description: "Root mutation of entity engine.", 76 | } 77 | 78 | return graphql.NewObject(rootMutation) 79 | } 80 | 81 | func deleteArgs(entity *graph.Entity) graphql.FieldConfigArgument { 82 | return graphql.FieldConfigArgument{ 83 | consts.ARG_WHERE: &graphql.ArgumentConfig{ 84 | Type: Cache.WhereExp(entity.Name()), 85 | }, 86 | } 87 | } 88 | 89 | func deleteByIdArgs() graphql.FieldConfigArgument { 90 | return graphql.FieldConfigArgument{ 91 | consts.ID: &graphql.ArgumentConfig{ 92 | Type: graphql.Int, 93 | }, 94 | } 95 | } 96 | 97 | func upsertArgs(entity *graph.Entity) graphql.FieldConfigArgument { 98 | return graphql.FieldConfigArgument{ 99 | consts.ARG_OBJECTS: &graphql.ArgumentConfig{ 100 | Type: &graphql.NonNull{ 101 | OfType: &graphql.List{ 102 | OfType: &graphql.NonNull{ 103 | OfType: Cache.SaveInput(entity.Name()), 104 | }, 105 | }, 106 | }, 107 | }, 108 | } 109 | } 110 | 111 | func upsertOneArgs(entity *graph.Entity) graphql.FieldConfigArgument { 112 | return graphql.FieldConfigArgument{ 113 | consts.ARG_OBJECT: &graphql.ArgumentConfig{ 114 | Type: &graphql.NonNull{ 115 | OfType: Cache.SaveInput(entity.Name()), 116 | }, 117 | }, 118 | } 119 | } 120 | 121 | func setArgs(entity *graph.Entity) graphql.FieldConfigArgument { 122 | updateInput := Cache.SetInput(entity.Name()) 123 | return graphql.FieldConfigArgument{ 124 | consts.ARG_SET: &graphql.ArgumentConfig{ 125 | Type: &graphql.NonNull{ 126 | OfType: updateInput, 127 | }, 128 | }, 129 | consts.ARG_WHERE: &graphql.ArgumentConfig{ 130 | Type: Cache.WhereExp(entity.Name()), 131 | }, 132 | } 133 | } 134 | 135 | func appendEntityMutationToFields(entity *graph.Entity, feilds graphql.Fields) { 136 | (feilds)[entity.DeleteName()] = &graphql.Field{ 137 | Type: Cache.MutationResponse(entity.Name()), 138 | Args: deleteArgs(entity), 139 | //Resolve: entity.QueryResolve(), 140 | } 141 | (feilds)[entity.DeleteByIdName()] = &graphql.Field{ 142 | Type: Cache.OutputType(entity.Name()), 143 | Args: deleteByIdArgs(), 144 | //Resolve: entity.QueryResolve(), 145 | } 146 | (feilds)[entity.UpsertName()] = &graphql.Field{ 147 | Type: &graphql.List{OfType: Cache.OutputType(entity.Name())}, 148 | Args: upsertArgs(entity), 149 | } 150 | //Resolve: entity.QueryResolve(), 151 | (feilds)[entity.UpsertOneName()] = &graphql.Field{ 152 | Type: Cache.OutputType(entity.Name()), 153 | Args: upsertOneArgs(entity), 154 | Resolve: resolve.PostOneResolveFn(entity), 155 | } 156 | 157 | updateInput := Cache.SetInput(entity.Name()) 158 | if len(updateInput.Fields()) > 0 { 159 | (feilds)[entity.SetName()] = &graphql.Field{ 160 | Type: Cache.MutationResponse(entity.Name()), 161 | Args: setArgs(entity), 162 | //Resolve: entity.QueryResolve(), 163 | } 164 | } 165 | 166 | } 167 | 168 | func appendPartialMutationToFields(partial *graph.Partial, feilds graphql.Fields) { 169 | 170 | (feilds)[partial.DeleteName()] = &graphql.Field{ 171 | Type: Cache.MutationResponse(partial.Name()), 172 | Args: deleteArgs(&partial.Entity), 173 | //Resolve: entity.QueryResolve(), 174 | } 175 | (feilds)[partial.DeleteByIdName()] = &graphql.Field{ 176 | Type: Cache.OutputType(partial.Name()), 177 | Args: deleteByIdArgs(), 178 | //Resolve: entity.QueryResolve(), 179 | } 180 | (feilds)[partial.InsertName()] = &graphql.Field{ 181 | Type: &graphql.List{OfType: Cache.OutputType(partial.Name())}, 182 | Args: upsertArgs(&partial.Entity), 183 | } 184 | //Resolve: entity.QueryResolve(), 185 | (feilds)[partial.InsertOneName()] = &graphql.Field{ 186 | Type: Cache.OutputType(partial.Name()), 187 | Args: upsertOneArgs(&partial.Entity), 188 | //Resolve: resolve.PostOneResolveFn(&partial.Entity), 189 | } 190 | (feilds)[partial.UpdateName()] = &graphql.Field{ 191 | Type: &graphql.List{OfType: Cache.OutputType(partial.Name())}, 192 | Args: upsertArgs(&partial.Entity), 193 | } 194 | //Resolve: entity.QueryResolve(), 195 | (feilds)[partial.UpdateOneName()] = &graphql.Field{ 196 | Type: Cache.OutputType(partial.Name()), 197 | Args: upsertOneArgs(&partial.Entity), 198 | //Resolve: resolve.PostOneResolveFn(&partial.Entity), 199 | } 200 | 201 | updateInput := Cache.SetInput(partial.Name()) 202 | if len(updateInput.Fields()) > 0 { 203 | (feilds)[partial.SetName()] = &graphql.Field{ 204 | Type: Cache.MutationResponse(partial.Name()), 205 | Args: setArgs(&partial.Entity), 206 | //Resolve: entity.QueryResolve(), 207 | } 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /schema/object.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/graph" 6 | ) 7 | 8 | func (c *TypeCache) makeEntityOutputObjects(entities []*graph.Entity) { 9 | for i := range entities { 10 | c.makeEntityObject(entities[i]) 11 | 12 | } 13 | } 14 | 15 | func (c *TypeCache) makePartialOutputObjects(partials []*graph.Partial) { 16 | for i := range partials { 17 | patial := partials[i] 18 | c.makeEntityObject(&patial.Entity) 19 | // partialName := patial.NameWithPartial() 20 | 21 | // objType := graphql.NewObject( 22 | // graphql.ObjectConfig{ 23 | // Name: partialName, 24 | // Fields: outputFields(patial.AllAttributes(), patial.AllMethods()), 25 | // Description: patial.Description(), 26 | // }, 27 | // ) 28 | // c.ObjectTypeMap[partialName] = objType 29 | } 30 | } 31 | 32 | func (c *TypeCache) makeExternalOutputObjects(externals []*graph.External) { 33 | for i := range externals { 34 | external := externals[i] 35 | c.makeEntityObject(&external.Entity) 36 | } 37 | } 38 | 39 | func (c *TypeCache) makeEntityObject(entity *graph.Entity) { 40 | objType := c.ObjectType(entity) 41 | c.ObjectTypeMap[entity.Name()] = objType 42 | c.ObjectMapById[entity.InnerId()] = objType 43 | } 44 | 45 | func (c *TypeCache) ObjectType(entity *graph.Entity) *graphql.Object { 46 | name := entity.Name() 47 | interfaces := c.mapInterfaces(entity.Interfaces) 48 | 49 | if len(interfaces) > 0 { 50 | return graphql.NewObject( 51 | graphql.ObjectConfig{ 52 | Name: name, 53 | Fields: outputFields(entity.AllAttributes(), entity.AllMethods()), 54 | Description: entity.Description(), 55 | Interfaces: interfaces, 56 | }, 57 | ) 58 | } else { 59 | return graphql.NewObject( 60 | graphql.ObjectConfig{ 61 | Name: name, 62 | Fields: outputFields(entity.AllAttributes(), entity.AllMethods()), 63 | Description: entity.Description(), 64 | }, 65 | ) 66 | } 67 | 68 | } 69 | 70 | func outputFields(attrs []*graph.Attribute, methods []*graph.Method) graphql.Fields { 71 | fields := graphql.Fields{} 72 | for _, attr := range attrs { 73 | fields[attr.Name] = &graphql.Field{ 74 | Type: PropertyType(attr), 75 | Description: attr.Description, 76 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 77 | // fmt.Println(p.Context.Value("data")) 78 | // return "world", nil 79 | // }, 80 | } 81 | } 82 | 83 | for _, method := range methods { 84 | fields[method.GetName()] = &graphql.Field{ 85 | Type: PropertyType(method), 86 | Description: method.Method.Description, 87 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 88 | // fmt.Println(p.Context.Value("data")) 89 | // return "world", nil 90 | // }, 91 | } 92 | } 93 | return fields 94 | } 95 | -------------------------------------------------------------------------------- /schema/orderby.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import "github.com/graphql-go/graphql" 4 | 5 | var EnumOrderBy = graphql.NewEnum( 6 | graphql.EnumConfig{ 7 | Name: "OrderBy", 8 | Values: graphql.EnumValueConfigMap{ 9 | "asc": &graphql.EnumValueConfig{ 10 | Value: "asc", 11 | }, 12 | "ascNullsFirst": &graphql.EnumValueConfig{ 13 | Value: "ascNullsFirst", 14 | }, 15 | "ascNullsLast": &graphql.EnumValueConfig{ 16 | Value: "ascNullsLast", 17 | }, 18 | "desc": &graphql.EnumValueConfig{ 19 | Value: "desc", 20 | }, 21 | "descNullsFirst": &graphql.EnumValueConfig{ 22 | Value: "descNullsFirst", 23 | }, 24 | "descNullsLast": &graphql.EnumValueConfig{ 25 | Value: "descNullsLast", 26 | }, 27 | }, 28 | }, 29 | ) 30 | -------------------------------------------------------------------------------- /schema/property.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/graph" 6 | "rxdrag.com/entify/model/meta" 7 | "rxdrag.com/entify/scalars" 8 | ) 9 | 10 | func InputPropertyType(property graph.Propertier) graphql.Type { 11 | if property.GetType() == meta.FILE { 12 | return scalars.UploadType 13 | } 14 | return PropertyType(property) 15 | } 16 | 17 | func PropertyType(property graph.Propertier) graphql.Output { 18 | switch property.GetType() { 19 | case meta.ID: 20 | return graphql.ID 21 | case meta.INT: 22 | return graphql.Int 23 | case meta.FLOAT: 24 | return graphql.Float 25 | case meta.BOOLEAN: 26 | return graphql.Boolean 27 | case meta.STRING: 28 | return graphql.String 29 | case meta.DATE: 30 | return graphql.DateTime 31 | case meta.VALUE_OBJECT, 32 | meta.ENTITY, 33 | meta.ID_ARRAY, 34 | meta.INT_ARRAY, 35 | meta.FLOAT_ARRAY, 36 | meta.STRING_ARRAY, 37 | meta.DATE_ARRAY, 38 | meta.ENUM_ARRAY, 39 | meta.VALUE_OBJECT_ARRAY, 40 | meta.ENTITY_ARRAY: 41 | return scalars.JSONType 42 | case meta.ENUM: 43 | enum := property.GetEumnType() 44 | if enum == nil { 45 | panic("Can not find enum entity") 46 | } 47 | return Cache.EnumType(enum.Name) 48 | case meta.FILE: 49 | //return graphql.String 50 | return fileOutputType 51 | } 52 | 53 | panic("No column type:" + property.GetName()) 54 | } 55 | 56 | func AttributeExp(column *graph.Attribute) *graphql.InputObjectFieldConfig { 57 | switch column.Type { 58 | case meta.INT: 59 | return &IntComparisonExp 60 | case meta.FLOAT: 61 | return &FloatComparisonExp 62 | case meta.BOOLEAN: 63 | return &BooleanComparisonExp 64 | case meta.STRING: 65 | return &StringComparisonExp 66 | case meta.DATE: 67 | return &DateTimeComparisonExp 68 | case meta.VALUE_OBJECT, 69 | meta.ENTITY, 70 | meta.ID_ARRAY, 71 | meta.INT_ARRAY, 72 | meta.FLOAT_ARRAY, 73 | meta.STRING_ARRAY, 74 | meta.DATE_ARRAY, 75 | meta.ENUM_ARRAY, 76 | meta.VALUE_OBJECT_ARRAY, 77 | meta.ENTITY_ARRAY, 78 | meta.FILE: 79 | return nil 80 | case meta.ID: 81 | return &IdComparisonExp 82 | case meta.ENUM: 83 | return EnumComparisonExp(column) 84 | } 85 | 86 | panic("No column type: " + column.Type) 87 | } 88 | 89 | func AttributeOrderBy(column *graph.Attribute) *graphql.Enum { 90 | switch column.Type { 91 | case meta.VALUE_OBJECT, 92 | meta.BOOLEAN, 93 | meta.ENTITY, 94 | meta.ID_ARRAY, 95 | meta.INT_ARRAY, 96 | meta.FLOAT_ARRAY, 97 | meta.STRING_ARRAY, 98 | meta.DATE_ARRAY, 99 | meta.ENUM_ARRAY, 100 | meta.VALUE_OBJECT_ARRAY, 101 | meta.ENTITY_ARRAY: 102 | return nil 103 | } 104 | 105 | return EnumOrderBy 106 | } 107 | -------------------------------------------------------------------------------- /schema/query.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/config" 6 | "rxdrag.com/entify/consts" 7 | "rxdrag.com/entify/model" 8 | "rxdrag.com/entify/model/graph" 9 | "rxdrag.com/entify/resolve" 10 | "rxdrag.com/entify/scalars" 11 | ) 12 | 13 | func rootQuery() *graphql.Object { 14 | rootQueryConfig := graphql.ObjectConfig{ 15 | Name: consts.ROOT_QUERY_NAME, 16 | Fields: queryFields(), 17 | } 18 | 19 | return graphql.NewObject(rootQueryConfig) 20 | } 21 | 22 | func queryFields() graphql.Fields { 23 | queryFields := graphql.Fields{ 24 | consts.SERVICE: serviceField(), 25 | consts.ENTITIES: &graphql.Field{ 26 | Type: &graphql.NonNull{ 27 | OfType: &graphql.List{ 28 | OfType: EntityType, 29 | }, 30 | }, 31 | Args: graphql.FieldConfigArgument{ 32 | consts.REPRESENTATIONS: &graphql.ArgumentConfig{ 33 | Type: &graphql.NonNull{ 34 | OfType: &graphql.List{ 35 | OfType: &graphql.NonNull{ 36 | OfType: scalars.AnyType, 37 | }, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | } 44 | 45 | for _, intf := range model.GlobalModel.Graph.RootInterfaces() { 46 | appendInterfaceToQueryFields(intf, queryFields) 47 | } 48 | 49 | for _, entity := range model.GlobalModel.Graph.RootEnities() { 50 | appendEntityToQueryFields(entity, queryFields) 51 | } 52 | for _, partial := range model.GlobalModel.Graph.RootPartails() { 53 | appendPartailToQueryFields(partial, queryFields) 54 | } 55 | // for _, service := range model.GlobalModel.Graph.RootExternals() { 56 | // appendServiceQueryFields(service, queryFields) 57 | // } 58 | 59 | if config.AuthUrl() == "" { 60 | appendAuthToQuery(queryFields) 61 | } 62 | 63 | return queryFields 64 | } 65 | 66 | func queryResponseType(class *graph.Class) graphql.Output { 67 | return &graphql.NonNull{ 68 | OfType: &graphql.List{ 69 | OfType: Cache.OutputType(class.Name()), 70 | }, 71 | } 72 | } 73 | 74 | func queryArgs(name string) graphql.FieldConfigArgument { 75 | config := graphql.FieldConfigArgument{ 76 | consts.ARG_DISTINCTON: &graphql.ArgumentConfig{ 77 | Type: Cache.DistinctOnEnum(name), 78 | }, 79 | consts.ARG_LIMIT: &graphql.ArgumentConfig{ 80 | Type: graphql.Int, 81 | }, 82 | consts.ARG_OFFSET: &graphql.ArgumentConfig{ 83 | Type: graphql.Int, 84 | }, 85 | consts.ARG_WHERE: &graphql.ArgumentConfig{ 86 | Type: Cache.WhereExp(name), 87 | }, 88 | } 89 | orderByExp := Cache.OrderByExp(name) 90 | 91 | if orderByExp != nil { 92 | config[consts.ARG_ORDERBY] = &graphql.ArgumentConfig{ 93 | Type: orderByExp, 94 | } 95 | } 96 | return config 97 | } 98 | 99 | func appendInterfaceToQueryFields(intf *graph.Interface, fields graphql.Fields) { 100 | (fields)[intf.QueryName()] = &graphql.Field{ 101 | Type: queryResponseType(&intf.Class), 102 | Args: queryArgs(intf.Name()), 103 | Resolve: resolve.QueryInterfaceResolveFn(intf), 104 | } 105 | (fields)[intf.QueryOneName()] = &graphql.Field{ 106 | Type: Cache.OutputType(intf.Name()), 107 | Args: queryArgs(intf.Name()), 108 | Resolve: resolve.QueryOneInterfaceResolveFn(intf), 109 | } 110 | 111 | (fields)[intf.QueryAggregateName()] = &graphql.Field{ 112 | Type: AggregateInterfaceType(intf), 113 | Args: queryArgs(intf.Name()), 114 | Resolve: resolve.QueryInterfaceResolveFn(intf), 115 | } 116 | } 117 | 118 | func appendEntityToQueryFields(entity *graph.Entity, fields graphql.Fields) { 119 | (fields)[entity.QueryName()] = &graphql.Field{ 120 | Type: queryResponseType(&entity.Class), 121 | Args: queryArgs(entity.Name()), 122 | Resolve: resolve.QueryEntityResolveFn(entity), 123 | } 124 | (fields)[entity.QueryOneName()] = &graphql.Field{ 125 | Type: Cache.OutputType(entity.Name()), 126 | Args: queryArgs(entity.Name()), 127 | Resolve: resolve.QueryOneEntityResolveFn(entity), 128 | } 129 | 130 | if notSystemEntity(entity) { 131 | (fields)[entity.QueryAggregateName()] = &graphql.Field{ 132 | Type: AggregateEntityType(entity), 133 | Args: queryArgs(entity.Name()), 134 | Resolve: resolve.QueryEntityResolveFn(entity), 135 | } 136 | } 137 | } 138 | 139 | func appendPartailToQueryFields(partial *graph.Partial, fields graphql.Fields) { 140 | (fields)[partial.QueryName()] = &graphql.Field{ 141 | Type: queryResponseType(&partial.Class), 142 | Args: queryArgs(partial.Name()), 143 | Resolve: resolve.QueryEntityResolveFn(&partial.Entity), 144 | } 145 | (fields)[partial.QueryOneName()] = &graphql.Field{ 146 | Type: Cache.OutputType(partial.Name()), 147 | Args: queryArgs(partial.Name()), 148 | Resolve: resolve.QueryOneEntityResolveFn(&partial.Entity), 149 | } 150 | 151 | (fields)[partial.QueryAggregateName()] = &graphql.Field{ 152 | Type: AggregatePartialType(partial), 153 | Args: queryArgs(partial.Name()), 154 | Resolve: resolve.QueryEntityResolveFn(&partial.Entity), 155 | } 156 | } 157 | 158 | func appendAuthToQuery(fields graphql.Fields) { 159 | fields[consts.ME] = &graphql.Field{ 160 | Type: baseUserType, 161 | Resolve: resolve.Me, 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /schema/queryargs.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model" 7 | "rxdrag.com/entify/model/graph" 8 | ) 9 | 10 | func (c *TypeCache) makeQueryArgs() { 11 | for i := range model.GlobalModel.Graph.Interfaces { 12 | c.makeOneInterfaceArgs(model.GlobalModel.Graph.Interfaces[i]) 13 | } 14 | for i := range model.GlobalModel.Graph.Entities { 15 | c.makeOneEntityArgs(model.GlobalModel.Graph.Entities[i]) 16 | } 17 | for i := range model.GlobalModel.Graph.Partials { 18 | c.makeOnePartailArgs(model.GlobalModel.Graph.Partials[i]) 19 | } 20 | 21 | for i := range model.GlobalModel.Graph.Externals { 22 | c.makeOneExternalArgs(model.GlobalModel.Graph.Externals[i]) 23 | } 24 | c.makeRelaionWhereExp() 25 | } 26 | 27 | func (c *TypeCache) makeOneEntityArgs(entity *graph.Entity) { 28 | c.makeOneArgs(entity.Name(), entity.AllAttributes()) 29 | } 30 | 31 | func (c *TypeCache) makeOneInterfaceArgs(intf *graph.Interface) { 32 | c.makeOneArgs(intf.Name(), intf.AllAttributes()) 33 | } 34 | 35 | func (c *TypeCache) makeOnePartailArgs(partial *graph.Partial) { 36 | c.makeOneArgs(partial.Name(), partial.AllAttributes()) 37 | } 38 | 39 | func (c *TypeCache) makeOneExternalArgs(external *graph.External) { 40 | c.makeOneArgs(external.Name(), external.AllAttributes()) 41 | } 42 | 43 | func (c *TypeCache) makeOneArgs(name string, attrs []*graph.Attribute) { 44 | whereExp := makeWhereExp(name, attrs) 45 | c.WhereExpMap[name] = whereExp 46 | 47 | orderByExp := makeOrderBy(name, attrs) 48 | if len(orderByExp.Fields()) > 0 { 49 | c.OrderByMap[name] = orderByExp 50 | } 51 | 52 | distinctOnEnum := makeDistinctOnEnum(name, attrs) 53 | c.DistinctOnEnumMap[name] = distinctOnEnum 54 | } 55 | 56 | func (c *TypeCache) makeRelaionWhereExp() { 57 | for className := range c.WhereExpMap { 58 | exp := c.WhereExpMap[className] 59 | intf := model.GlobalModel.Graph.GetInterfaceByName(className) 60 | entity := model.GlobalModel.Graph.GetEntityByName(className) 61 | partial := model.GlobalModel.Graph.GetPartialByName(className) 62 | external := model.GlobalModel.Graph.GetExternalByName(className) 63 | if intf == nil && entity == nil && partial == nil && external == nil { 64 | panic("Fatal error, can not find class by name:" + className) 65 | } 66 | var associations []*graph.Association 67 | if intf != nil { 68 | associations = intf.AllAssociations() 69 | } else if entity != nil { 70 | associations = entity.AllAssociations() 71 | } else if partial != nil { 72 | associations = partial.AllAssociations() 73 | } else if external != nil { 74 | associations = external.AllAssociations() 75 | } 76 | for i := range associations { 77 | assoc := associations[i] 78 | exp.AddFieldConfig(assoc.Name(), &graphql.InputObjectFieldConfig{ 79 | Type: c.WhereExp(assoc.TypeClass().Name()), 80 | }) 81 | } 82 | } 83 | } 84 | 85 | func makeWhereExp(name string, attrs []*graph.Attribute) *graphql.InputObject { 86 | expName := name + consts.BOOLEXP 87 | andExp := graphql.InputObjectFieldConfig{} 88 | notExp := graphql.InputObjectFieldConfig{} 89 | orExp := graphql.InputObjectFieldConfig{} 90 | 91 | fields := graphql.InputObjectConfigFieldMap{ 92 | consts.ARG_AND: &andExp, 93 | consts.ARG_NOT: ¬Exp, 94 | consts.ARG_OR: &orExp, 95 | } 96 | 97 | boolExp := graphql.NewInputObject( 98 | graphql.InputObjectConfig{ 99 | Name: expName, 100 | Fields: fields, 101 | }, 102 | ) 103 | andExp.Type = &graphql.List{ 104 | OfType: &graphql.NonNull{ 105 | OfType: boolExp, 106 | }, 107 | } 108 | notExp.Type = boolExp 109 | orExp.Type = &graphql.List{ 110 | OfType: &graphql.NonNull{ 111 | OfType: boolExp, 112 | }, 113 | } 114 | 115 | for i := range attrs { 116 | attr := attrs[i] 117 | columnExp := AttributeExp(attr) 118 | 119 | if columnExp != nil { 120 | fields[attr.Name] = columnExp 121 | } 122 | } 123 | return boolExp 124 | } 125 | 126 | func makeOrderBy(name string, attrs []*graph.Attribute) *graphql.InputObject { 127 | fields := graphql.InputObjectConfigFieldMap{} 128 | 129 | orderByExp := graphql.NewInputObject( 130 | graphql.InputObjectConfig{ 131 | Name: name + consts.ORDERBY, 132 | Fields: fields, 133 | }, 134 | ) 135 | 136 | for i := range attrs { 137 | attr := attrs[i] 138 | attrOrderBy := AttributeOrderBy(attr) 139 | if attrOrderBy != nil { 140 | fields[attr.Name] = &graphql.InputObjectFieldConfig{Type: attrOrderBy} 141 | } 142 | } 143 | return orderByExp 144 | } 145 | 146 | func makeDistinctOnEnum(name string, attrs []*graph.Attribute) *graphql.Enum { 147 | enumValueConfigMap := graphql.EnumValueConfigMap{} 148 | for i := range attrs { 149 | attr := attrs[i] 150 | enumValueConfigMap[attr.Name] = &graphql.EnumValueConfig{ 151 | Value: attr.Name, 152 | } 153 | } 154 | 155 | entEnum := graphql.NewEnum( 156 | graphql.EnumConfig{ 157 | Name: name + consts.DISTINCTEXP, 158 | Values: enumValueConfigMap, 159 | }, 160 | ) 161 | return entEnum 162 | } 163 | -------------------------------------------------------------------------------- /schema/relation.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model" 6 | "rxdrag.com/entify/model/graph" 7 | "rxdrag.com/entify/resolve" 8 | ) 9 | 10 | func (c *TypeCache) makeRelations() { 11 | for i := range model.GlobalModel.Graph.Interfaces { 12 | intf := model.GlobalModel.Graph.Interfaces[i] 13 | interfaceType := c.InterfaceTypeMap[intf.Name()] 14 | if interfaceType == nil { 15 | panic("Can find object type:" + intf.Name()) 16 | } 17 | for _, association := range intf.AllAssociations() { 18 | if interfaceType.Fields()[association.Name()] != nil { 19 | panic("Duplicate interface field: " + intf.Name() + "." + association.Name()) 20 | } 21 | interfaceType.AddFieldConfig(association.Name(), &graphql.Field{ 22 | Name: association.Name(), 23 | Type: c.AssociationType(association), 24 | Description: association.Description(), 25 | Resolve: resolve.QueryAssociationFn(association), 26 | Args: queryArgs(association.TypeClass().Name()), 27 | }) 28 | } 29 | } 30 | for i := range model.GlobalModel.Graph.Entities { 31 | entity := model.GlobalModel.Graph.Entities[i] 32 | objectType := c.ObjectTypeMap[entity.Name()] 33 | for _, association := range entity.AllAssociations() { 34 | if objectType.Fields()[association.Name()] != nil { 35 | panic("Duplicate entity field: " + entity.Name() + "." + association.Name()) 36 | } 37 | objectType.AddFieldConfig(association.Name(), &graphql.Field{ 38 | Name: association.Name(), 39 | Type: c.AssociationType(association), 40 | Description: association.Description(), 41 | Resolve: resolve.QueryAssociationFn(association), 42 | Args: queryArgs(association.TypeClass().Name()), 43 | }) 44 | } 45 | } 46 | } 47 | 48 | func (c *TypeCache) AssociationType(association *graph.Association) graphql.Output { 49 | if association.IsArray() { 50 | return &graphql.NonNull{ 51 | OfType: &graphql.List{ 52 | OfType: c.OutputType(association.TypeClass().Name()), 53 | }, 54 | } 55 | } else { 56 | return c.OutputType(association.TypeClass().Name()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /schema/resolve.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/resolve" 6 | ) 7 | 8 | func publishResolve(p graphql.ResolveParams) (interface{}, error) { 9 | result, err := resolve.PublishMetaResolve(p) 10 | if err != nil { 11 | return result, err 12 | } 13 | 14 | InitSchema() 15 | return result, nil 16 | } 17 | 18 | func installResolve(p graphql.ResolveParams) (interface{}, error) { 19 | result, err := resolve.InstallResolve(p) 20 | if err != nil { 21 | return result, err 22 | } 23 | InitSchema() 24 | return result, err 25 | } 26 | -------------------------------------------------------------------------------- /schema/schema.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/consts" 6 | "rxdrag.com/entify/model" 7 | "rxdrag.com/entify/scalars" 8 | "rxdrag.com/entify/utils" 9 | ) 10 | 11 | func MakeSchema() { 12 | Cache.MakeCache() 13 | 14 | EntityType = graphql.NewUnion( 15 | graphql.UnionConfig{ 16 | Name: consts.ENTITY_TYPE, 17 | Types: Cache.EntityObjects(), 18 | ResolveType: resolveTypeFn, 19 | }, 20 | ) 21 | 22 | schemaConfig := graphql.SchemaConfig{ 23 | Query: rootQuery(), 24 | Mutation: rootMutation(), 25 | Subscription: RootSubscription(), 26 | Directives: []*graphql.Directive{ 27 | graphql.NewDirective(graphql.DirectiveConfig{ 28 | Name: consts.EXTERNAL, 29 | Locations: []string{graphql.DirectiveLocationField}, 30 | }), 31 | graphql.NewDirective(graphql.DirectiveConfig{ 32 | Name: consts.REQUIRES, 33 | Locations: []string{graphql.DirectiveLocationField}, 34 | Args: graphql.FieldConfigArgument{ 35 | consts.ARG_FIELDS: &graphql.ArgumentConfig{ 36 | Type: &graphql.NonNull{ 37 | OfType: scalars.FieldSetType, 38 | }, 39 | }, 40 | }, 41 | }), 42 | graphql.NewDirective(graphql.DirectiveConfig{ 43 | Name: consts.PROVIDES, 44 | Locations: []string{graphql.DirectiveLocationField}, 45 | Args: graphql.FieldConfigArgument{ 46 | consts.ARG_FIELDS: &graphql.ArgumentConfig{ 47 | Type: &graphql.NonNull{ 48 | OfType: scalars.FieldSetType, 49 | }, 50 | }, 51 | }, 52 | }), 53 | graphql.NewDirective(graphql.DirectiveConfig{ 54 | Name: consts.KEY, 55 | Locations: []string{graphql.DirectiveLocationObject, graphql.DirectiveLocationInterface}, 56 | Args: graphql.FieldConfigArgument{ 57 | consts.ARG_FIELDS: &graphql.ArgumentConfig{ 58 | Type: &graphql.NonNull{ 59 | OfType: scalars.FieldSetType, 60 | }, 61 | }, 62 | }, 63 | }), 64 | graphql.NewDirective(graphql.DirectiveConfig{ 65 | Name: consts.EXTENDS, 66 | Locations: []string{graphql.DirectiveLocationObject, graphql.DirectiveLocationInterface}, 67 | }), 68 | }, 69 | Types: append(Cache.EntityTypes(), scalars.FieldSetType), 70 | } 71 | theSchema, err := graphql.NewSchema(schemaConfig) 72 | 73 | if err != nil { 74 | panic(err) 75 | //log.Fatalf("failed to create new schema, error: %v", err) 76 | } 77 | model.GlobalModel.Schema = &theSchema 78 | } 79 | 80 | func ResolveSchema() *graphql.Schema { 81 | return model.GlobalModel.Schema 82 | } 83 | 84 | func InitSchema() { 85 | LoadModel() 86 | MakeSchema() 87 | } 88 | 89 | func InitAuthInstallSchema() { 90 | LoadModel() 91 | schemaConfig := graphql.SchemaConfig{ 92 | Query: graphql.NewObject( 93 | graphql.ObjectConfig{ 94 | Name: consts.ROOT_QUERY_NAME, 95 | Fields: graphql.Fields{ 96 | consts.INSTALLED: &graphql.Field{ 97 | Type: graphql.Boolean, 98 | Resolve: func(p graphql.ResolveParams) (interface{}, error) { 99 | defer utils.PrintErrorStack() 100 | return false, nil 101 | }, 102 | }, 103 | }, 104 | }, 105 | ), 106 | Mutation: graphql.NewObject(graphql.ObjectConfig{ 107 | Name: consts.ROOT_MUTATION_NAME, 108 | Fields: graphql.Fields{ 109 | "installAuth": &graphql.Field{ 110 | Type: graphql.Boolean, 111 | Args: graphql.FieldConfigArgument{ 112 | INPUT: &graphql.ArgumentConfig{ 113 | Type: &graphql.NonNull{ 114 | OfType: installInputType, 115 | }, 116 | }, 117 | }, 118 | Resolve: installResolve, 119 | }, 120 | }, 121 | Description: "Root mutation of entity engine. For install auth entify", 122 | }), 123 | } 124 | theSchema, err := graphql.NewSchema(schemaConfig) 125 | 126 | if err != nil { 127 | panic(err) 128 | //log.Fatalf("failed to create new schema, error: %v", err) 129 | } 130 | model.GlobalModel.Schema = &theSchema 131 | } 132 | -------------------------------------------------------------------------------- /schema/service.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "rxdrag.com/entify/model/graph" 6 | "rxdrag.com/entify/model/meta" 7 | "rxdrag.com/entify/utils" 8 | ) 9 | 10 | func appendServiceQueryFields(exClass *graph.Entity, fields graphql.Fields) { 11 | methods := exClass.MethodsByType(meta.QUERY) 12 | if len(methods) > 0 { 13 | (fields)[exClass.QueryName()] = &graphql.Field{ 14 | Type: graphql.String, 15 | //Resolve: resolve.QueryResolveFn(node), 16 | } 17 | } 18 | 19 | } 20 | 21 | func appendServiceMutationFields(serviceClass *graph.Entity, fields graphql.Fields) { 22 | methods := serviceClass.MethodsByType(meta.MUTATION) 23 | if len(methods) > 0 { 24 | (fields)[utils.FirstLower(serviceClass.Name())] = &graphql.Field{ 25 | Type: graphql.String, 26 | //Resolve: resolve.QueryResolveFn(node), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /schema/subscription.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | 6 | "rxdrag.com/entify/consts" 7 | 8 | "github.com/graphql-go/graphql" 9 | ) 10 | 11 | type Feed struct { 12 | ID string `graphql:"id"` 13 | } 14 | 15 | var FeedType = graphql.NewObject(graphql.ObjectConfig{ 16 | Name: "FeedType", 17 | Fields: graphql.Fields{ 18 | "id": &graphql.Field{ 19 | Type: graphql.ID, 20 | }, 21 | }, 22 | }) 23 | 24 | func RootSubscription() *graphql.Object { 25 | // subscriptionFields := graphql.Fields{"feed": &graphql.Field{ 26 | // Type: FeedType, 27 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 28 | // fmt.Println("Resolve", p.Source) 29 | // return p.Source, nil 30 | // }, 31 | // Subscribe: func(p graphql.ResolveParams) (interface{}, error) { 32 | // fmt.Println("Subscribe it") 33 | // c := make(chan interface{}) 34 | 35 | // go func() { 36 | // var i int 37 | // for { 38 | // i++ 39 | // feed := Feed{ID: fmt.Sprintf("%d", i)} 40 | // select { 41 | // case <-p.Context.Done(): 42 | // log.Println("[RootSubscription] [Subscribe] subscription canceled") 43 | // close(c) 44 | // return 45 | // default: 46 | // c <- feed 47 | // } 48 | 49 | // time.Sleep(250 * time.Millisecond) 50 | 51 | // if i == 21 { 52 | // close(c) 53 | // return 54 | // } 55 | // } 56 | // }() 57 | 58 | // return c, nil 59 | // }, 60 | // }} 61 | 62 | subscriptionObj := graphql.NewObject(graphql.ObjectConfig{ 63 | Name: consts.ROOT_SUBSCRIPTION_NAME, 64 | Fields: queryFields(), 65 | }) 66 | 67 | //添加订阅代码 68 | fields := subscriptionObj.Fields() 69 | for i := range fields { 70 | field := fields[i] 71 | field.Subscribe = func(p graphql.ResolveParams) (interface{}, error) { 72 | fmt.Println("Resolve", p.Source) 73 | return p.Source, nil 74 | } 75 | } 76 | 77 | return subscriptionObj 78 | } 79 | 80 | // func appendToSubscriptionFields(node graph.Node, fields *graphql.Fields) { 81 | 82 | // (*fields)[utils.FirstLower(node.Name())] = &graphql.Field{ 83 | // Type: queryResponseType(node), 84 | // Args: quryeArgs(node), 85 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 86 | // return p.Source, nil 87 | // }, 88 | // } 89 | // (*fields)[consts.ONE+node.Name()] = &graphql.Field{ 90 | // Type: Cache.OutputType(node.Name()), 91 | // Args: quryeArgs(node), 92 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 93 | // return p.Source, nil 94 | // }, 95 | // } 96 | 97 | // (*fields)[utils.FirstLower(node.Name())+utils.FirstUpper(consts.AGGREGATE)] = &graphql.Field{ 98 | // Type: *AggregateType(node), 99 | // Args: quryeArgs(node), 100 | // Resolve: func(p graphql.ResolveParams) (interface{}, error) { 101 | // return p.Source, nil 102 | // }, 103 | // } 104 | // } 105 | -------------------------------------------------------------------------------- /schema/upload.go: -------------------------------------------------------------------------------- 1 | package schema 2 | -------------------------------------------------------------------------------- /sdlview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SDL格式化 7 | 8 | 9 | 10 | 50 | 51 | 52 | 53 |
54 |
55 | 58 |
59 |
60 |
61 | 62 |
63 | 64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /storage/file.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "mime/multipart" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/google/uuid" 11 | "rxdrag.com/entify/consts" 12 | ) 13 | 14 | type File struct { 15 | File multipart.File 16 | Filename string 17 | Size int64 18 | } 19 | 20 | type FileInfo struct { 21 | Path string `json:"path"` 22 | Filename string `json:"fileName"` 23 | Size int64 `json:"size"` 24 | MimeType string `json:"mimeType"` 25 | ExtName string `json:"extName"` 26 | } 27 | 28 | var mimeTypes = map[string]string{ 29 | ".css": "text/css; charset=utf-8", 30 | ".gif": "image/gif", 31 | ".htm": "text/html; charset=utf-8", 32 | ".html": "text/html; charset=utf-8", 33 | ".jpg": "image/jpeg", 34 | ".js": "application/x-javascript", 35 | ".pdf": "application/pdf", 36 | ".png": "image/png", 37 | ".xml": "text/xml; charset=utf-8", 38 | } 39 | 40 | func (f *File) extName() string { 41 | return filepath.Ext(f.Filename) 42 | } 43 | 44 | func (f *File) mimeType() string { 45 | //mtype, err := mimetype.DetectReader(f.File) 46 | 47 | return mimeTypes[f.extName()] 48 | } 49 | 50 | func (f *File) Save() FileInfo { 51 | name := fmt.Sprintf("%s%s", uuid.New().String(), f.extName()) 52 | localPath := fmt.Sprintf("%s/%s", consts.UPLOAD_PATH, name) 53 | file, err := os.OpenFile( 54 | localPath, 55 | os.O_WRONLY|os.O_CREATE, 56 | 0666, 57 | ) 58 | defer file.Close() 59 | if err != nil { 60 | panic(err.Error()) 61 | } 62 | io.Copy(file, f.File) 63 | return FileInfo{Path: name, Filename: f.Filename, Size: f.Size, MimeType: f.mimeType(), ExtName: f.extName()} 64 | } 65 | -------------------------------------------------------------------------------- /utils/array.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func StringFilter(arr []string, f func(value string) bool) []string { 4 | positive := []string{} 5 | 6 | for i := range arr { 7 | if f(arr[i]) { 8 | positive = append(positive, arr[i]) 9 | } 10 | } 11 | 12 | return positive 13 | } 14 | -------------------------------------------------------------------------------- /utils/debug.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "runtime/debug" 7 | ) 8 | 9 | func PrintErrorStack() { 10 | if x := recover(); x != nil { 11 | println(fmt.Sprintf("%T: %+v", x, x)) 12 | log.Printf("%s\n", debug.Stack()) 13 | panic(x) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /utils/id.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "rxdrag.com/entify/config" 4 | 5 | const ( 6 | SERVICE_BITS = 52 7 | ENTITY_ID_BITS = 32 8 | ) 9 | 10 | func EncodeBaseId(entityInnerId uint64) uint64 { 11 | return uint64(config.ServiceId())<> ENTITY_ID_BITS 16 | } 17 | -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | type JSON map[string]interface{} 10 | 11 | func (m JSON) Value() (driver.Value, error) { 12 | if len(m) == 0 { 13 | return nil, nil 14 | } 15 | j, err := json.Marshal(m) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return driver.Value([]byte(j)), nil 20 | } 21 | 22 | func (m *JSON) Scan(src interface{}) error { 23 | var source []byte 24 | _m := make(map[string]interface{}) 25 | 26 | switch src.(type) { 27 | case []uint8: 28 | source = []byte(src.([]uint8)) 29 | case nil: 30 | return nil 31 | default: 32 | return errors.New("incompatible type for JSON") 33 | } 34 | err := json.Unmarshal(source, &_m) 35 | if err != nil { 36 | return err 37 | } 38 | *m = JSON(_m) 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /utils/map.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func MapStringKeys(m map[string]interface{}, wapper string) []string { 4 | keys := make([]string, 0, len(m)) 5 | for k := range m { 6 | keys = append(keys, wapper+k+wapper) 7 | } 8 | return keys 9 | } 10 | 11 | func MapValues(m map[string]interface{}, wapper string) []interface{} { 12 | values := make([]interface{}, 0, len(m)) 13 | for k := range m { 14 | values = append(values, m[k]) 15 | } 16 | return values 17 | } 18 | -------------------------------------------------------------------------------- /utils/object.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type Object = map[string]interface{} 4 | -------------------------------------------------------------------------------- /utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // FirstUpper 字符串首字母大写 8 | func FirstUpper(s string) string { 9 | if s == "" { 10 | return "" 11 | } 12 | return strings.ToUpper(s[:1]) + s[1:] 13 | } 14 | 15 | // FirstLower 字符串首字母小写 16 | func FirstLower(s string) string { 17 | if s == "" { 18 | return "" 19 | } 20 | return strings.ToLower(s[:1]) + s[1:] 21 | } 22 | 23 | /** 24 | * 驼峰转蛇形 snake string 25 | * @description XxYy to xx_yy , XxYY to xx_y_y 26 | * @date 2020/7/30 27 | * @param s 需要转换的字符串 28 | * @return string 29 | **/ 30 | func SnakeString(s string) string { 31 | data := make([]byte, 0, len(s)*2) 32 | j := false 33 | num := len(s) 34 | for i := 0; i < num; i++ { 35 | d := s[i] 36 | // or通过ASCII码进行大小写的转化 37 | // 65-90(A-Z),97-122(a-z) 38 | //判断如果字母为大写的A-Z就在前面拼接一个_ 39 | if i > 0 && d >= 'A' && d <= 'Z' && j { 40 | data = append(data, '_') 41 | } 42 | if d != '_' { 43 | j = true 44 | } 45 | data = append(data, d) 46 | } 47 | //ToLower把大写字母统一转小写 48 | return strings.ToLower(string(data[:])) 49 | } 50 | 51 | /** 52 | * 蛇形转驼峰 53 | * @description xx_yy to XxYx xx_y_y to XxYY 54 | * @date 2020/7/30 55 | * @param s要转换的字符串 56 | * @return string 57 | **/ 58 | func CamelString(s string) string { 59 | data := make([]byte, 0, len(s)) 60 | j := false 61 | k := false 62 | num := len(s) - 1 63 | for i := 0; i <= num; i++ { 64 | d := s[i] 65 | if k == false && d >= 'A' && d <= 'Z' { 66 | k = true 67 | } 68 | if d >= 'a' && d <= 'z' && (j || k == false) { 69 | d = d - 32 70 | j = false 71 | k = true 72 | } 73 | if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' { 74 | j = true 75 | continue 76 | } 77 | data = append(data, d) 78 | } 79 | return string(data[:]) 80 | } 81 | -------------------------------------------------------------------------------- /备忘.md: -------------------------------------------------------------------------------- 1 | docker内连宿主机mysql host.docker.internal 2 | 3 | 创建镜像: 4 | docker build --pull --rm -f "Dockerfile" -t entify:lastest "." 5 | 6 | 创建容器 7 | docker create -p 4000:4000 --name entify entify:lastest 8 | 9 | 安装权限模块指令: 10 | 11 | mutation{ 12 | installAuth( 13 | input:{ 14 | admin:"admin", 15 | password:"123456", 16 | } 17 | ) 18 | } 19 | 20 | 用新的数据库创建一个容器 21 | docker create -p 4001:4000 -e SERVICE_ID=1 -e DATABASE="entify1" -e HOST="host.docker.internal" -e PORT="3306" -e USER="root" -e PASSWORD="RxDragDb" -e AUTH_URL="http://host.docker.internal:4000/graphql" -e storage="local" --name entify1 entify:lastest --------------------------------------------------------------------------------