├── .air.toml ├── .gitignore ├── .golangci.yml ├── .pre-commit-config.yaml ├── Makefile ├── README.md ├── bin ├── init_db.sh ├── init_db_e2e.sh └── init_db_test.sh ├── cmd ├── app │ └── main.go └── migration │ └── main.go ├── config ├── config.e2e.yml ├── config.go ├── config.test.yml └── config.yml ├── docker ├── docker-compose.yml └── mysql_data │ └── sql │ ├── reset_database.e2e.sql │ ├── reset_database.sql │ ├── reset_database.test.sql │ ├── safe_updates.e2e.sql │ ├── safe_updates.sql │ └── safe_updates.test.sql ├── ent ├── client.go ├── config.go ├── context.go ├── ent.go ├── entc.go ├── enttest │ └── enttest.go ├── generate.go ├── gql_collection.go ├── gql_edge.go ├── gql_node.go ├── gql_pagination.go ├── gql_transaction.go ├── gql_where_input.go ├── hook │ └── hook.go ├── migrate │ ├── migrate.go │ └── schema.go ├── mixin │ ├── datetime.go │ └── ulid.go ├── mutation.go ├── mutation_input.go ├── predicate │ └── predicate.go ├── runtime.go ├── runtime │ └── runtime.go ├── schema │ ├── todo.go │ ├── ulid │ │ └── ulid.go │ └── user.go ├── template │ └── mutation_input.tmpl ├── todo.go ├── todo │ ├── todo.go │ └── where.go ├── todo_create.go ├── todo_delete.go ├── todo_query.go ├── todo_update.go ├── tx.go ├── ulid.go ├── user.go ├── user │ ├── user.go │ └── where.go ├── user_create.go ├── user_delete.go ├── user_query.go └── user_update.go ├── go.mod ├── go.sum ├── gqlgen.yml ├── graph ├── ent.graphqls ├── generated │ └── generated.go ├── schema.graphqls ├── todo.graphqls └── user.graphqls ├── pkg ├── adapter │ ├── controller │ │ ├── controller.go │ │ ├── todo.go │ │ └── user.go │ ├── handler │ │ └── error.go │ ├── repository │ │ ├── todo.go │ │ ├── user.go │ │ ├── user_test.go │ │ └── with_transactional_mutation.go │ └── resolver │ │ ├── resolver.go │ │ ├── schema.resolvers.go │ │ ├── todo.resolvers.go │ │ └── user.resolvers.go ├── const │ └── globalid │ │ └── globalid.go ├── entity │ └── model │ │ ├── error.go │ │ ├── id.go │ │ ├── node.go │ │ ├── pagination.go │ │ ├── todo.go │ │ └── user.go ├── infrastructure │ ├── datastore │ │ └── datastore.go │ ├── graphql │ │ └── graphql.go │ └── router │ │ └── router.go ├── registry │ ├── registry.go │ ├── todo.go │ └── user.go ├── usecase │ ├── repository │ │ ├── todo.go │ │ └── user.go │ └── usecase │ │ ├── todo.go │ │ └── user.go └── util │ ├── datetime │ └── datetime.go │ └── environment │ └── environment.go ├── test └── e2e │ └── mutation │ └── user_test.go └── testutil ├── config.go ├── database.go └── e2e └── e2e.go /.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | tmp_dir = "tmp" 3 | 4 | [build] 5 | bin = "./tmp/main" 6 | cmd = "go build -o ./tmp/main ./cmd/app/main.go" 7 | delay = 1000 8 | exclude_dir = ["assets", "tmp", "vendor", "docker", "bin"] 9 | exclude_file = [] 10 | exclude_regex = [] 11 | exclude_unchanged = false 12 | follow_symlink = false 13 | full_bin = "APP_ENV=development ./tmp/main" 14 | include_dir = [] 15 | include_ext = ["go", "tpl", "tmpl", "html"] 16 | kill_delay = "0s" 17 | log = "build-errors.log" 18 | send_interrupt = false 19 | stop_on_error = true 20 | 21 | [color] 22 | app = "" 23 | build = "yellow" 24 | main = "magenta" 25 | runner = "green" 26 | watcher = "cyan" 27 | 28 | [log] 29 | time = false 30 | 31 | [misc] 32 | clean_on_exit = false 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | gin-bin 4 | vendor 5 | tmp 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - goimports 5 | - revive 6 | - govet 7 | - errcheck 8 | - ineffassign 9 | - unconvert 10 | - misspell 11 | - prealloc 12 | - gosec 13 | 14 | issues: 15 | exclude-use-default: false 16 | 17 | linters-settings: 18 | govet: 19 | check-shadowing: true 20 | 21 | errcheck: 22 | ignore: "fmt:.*,Close" 23 | 24 | revive: 25 | # Use recommended configuration 26 | # @see https://github.com/mgechev/revive#recommended-configuration 27 | rules: 28 | - name: blank-imports 29 | - name: context-as-argument 30 | - name: context-keys-type 31 | - name: dot-imports 32 | - name: error-return 33 | - name: error-strings 34 | - name: error-naming 35 | - name: exported 36 | - name: if-return 37 | - name: increment-decrement 38 | - name: var-naming 39 | - name: var-declaration 40 | - name: package-comments 41 | - name: range 42 | - name: receiver-naming 43 | - name: time-naming 44 | - name: unexported-return 45 | - name: indent-error-flow 46 | - name: errorf 47 | - name: empty-block 48 | - name: superfluous-else 49 | # - name: unused-parameter 50 | - name: unreachable-code 51 | - name: redefines-builtin-id 52 | 53 | 54 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/golangci/golangci-lint 3 | rev: v1.42.1 4 | hooks: 5 | - id: golangci-lint 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | brew install yq 3 | brew install pre-commit 4 | pre-commit --version 5 | pre-commit install 6 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.1 7 | go install golang.org/x/tools/cmd/goimports@latest 8 | go install github.com/cosmtrek/air@v1.27.3 9 | 10 | # Set up database 11 | setup_db: 12 | ./bin/init_db.sh 13 | 14 | # Migrate scheme in ent to database 15 | migrate_schema: 16 | go run ./cmd/migration/main.go 17 | 18 | # Start dev server 19 | start: 20 | air 21 | 22 | # Testing 23 | setup_test_db: 24 | ./bin/init_db_test.sh 25 | 26 | test_repository: 27 | go test ./pkg/adapter/repository/... 28 | 29 | # E2E 30 | setup_e2e_db: 31 | ./bin/init_db_e2e.sh 32 | 33 | e2e: 34 | go test ./test/e2e/... 35 | 36 | .PHONY: install start setup_db migrate_schema setup_test_db setup_e2e_db e2e 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-clean-architecture-ent-gqlgen 2 | Clean Architecture with ent and gqlgen 3 | 4 | ## Run Docker 5 | 6 | ``` 7 | $ cd docker 8 | $ docker comopse up 9 | ``` 10 | 11 | ## Install 12 | 13 | ``` 14 | $ make install 15 | ``` 16 | 17 | ## Set up database 18 | 19 | ``` 20 | $ make setup_db 21 | $ make migrate_schema 22 | ``` 23 | 24 | ## Start server 25 | 26 | ``` 27 | $ make start 28 | ``` 29 | 30 | ## Testing 31 | 32 | ``` 33 | $ make setup_test_db 34 | $ make test_repository 35 | ``` 36 | 37 | ## E2E 38 | 39 | ``` 40 | $ make setup_e2e_db 41 | $ make e2e 42 | ``` 43 | -------------------------------------------------------------------------------- /bin/init_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd docker || exit 3 | docker compose cp ./mysql_data/sql mysql:/var/lib/mysql/ 4 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/reset_database.sql" 5 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/safe_updates.sql" 6 | -------------------------------------------------------------------------------- /bin/init_db_e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd docker || exit 3 | docker compose cp ./mysql_data/sql mysql:/var/lib/mysql/ 4 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/reset_database.e2e.sql" 5 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/safe_updates.e2e.sql" 6 | -------------------------------------------------------------------------------- /bin/init_db_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd docker || exit 3 | docker compose cp ./mysql_data/sql mysql:/var/lib/mysql/ 4 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/reset_database.test.sql" 5 | docker compose exec mysql bash -c "mysql -uroot -proot < /var/lib/mysql/sql/safe_updates.test.sql" 6 | -------------------------------------------------------------------------------- /cmd/app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/config" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 7 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/datastore" 8 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/graphql" 9 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/router" 10 | "golang-clean-architecture-ent-gqlgen/pkg/registry" 11 | "log" 12 | ) 13 | 14 | func main() { 15 | config.ReadConfig(config.ReadConfigOption{}) 16 | 17 | client := newDBClient() 18 | ctrl := newController(client) 19 | 20 | srv := graphql.NewServer(client, ctrl) 21 | e := router.New(srv) 22 | 23 | e.Logger.Fatal(e.Start(":" + config.C.Server.Address)) 24 | } 25 | 26 | func newDBClient() *ent.Client { 27 | client, err := datastore.NewClient() 28 | if err != nil { 29 | log.Fatalf("failed opening mysql client: %v", err) 30 | } 31 | 32 | return client 33 | } 34 | 35 | func newController(client *ent.Client) controller.Controller { 36 | r := registry.New(client) 37 | return r.NewController() 38 | } 39 | -------------------------------------------------------------------------------- /cmd/migration/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/config" 6 | "golang-clean-architecture-ent-gqlgen/ent" 7 | "golang-clean-architecture-ent-gqlgen/ent/migrate" 8 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/datastore" 9 | "log" 10 | ) 11 | 12 | func main() { 13 | config.ReadConfig(config.ReadConfigOption{}) 14 | 15 | client, err := datastore.NewClient() 16 | if err != nil { 17 | log.Fatalf("failed opening mysql client: %v", err) 18 | } 19 | defer client.Close() 20 | createDBSchema(client) 21 | } 22 | 23 | func createDBSchema(client *ent.Client) { 24 | if err := client.Schema.Create( 25 | context.Background(), 26 | migrate.WithDropIndex(true), 27 | migrate.WithDropColumn(true), 28 | migrate.WithForeignKeys(true), 29 | ); err != nil { 30 | log.Fatalf("failed creating schema resources: %v", err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /config/config.e2e.yml: -------------------------------------------------------------------------------- 1 | database: 2 | user: root 3 | password: root 4 | net: tcp 5 | addr: 127.0.0.1:3306 6 | dbname: golang_clean_architecture_ent_gqlgen_e2e 7 | allowNativePasswords: true 8 | params: 9 | parseTime: true 10 | charset: utf8mb4 11 | loc: Asia/Tokyo 12 | 13 | server: 14 | address: 8082 15 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "golang-clean-architecture-ent-gqlgen/pkg/util/environment" 6 | "log" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "runtime" 11 | 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | type config struct { 16 | Database struct { 17 | User string 18 | Password string 19 | Net string 20 | Addr string 21 | DBName string 22 | AllowNativePasswords bool 23 | Params struct { 24 | ParseTime string 25 | Charset string 26 | Loc string 27 | } 28 | } 29 | Server struct { 30 | Address string 31 | } 32 | } 33 | 34 | // C is config variable 35 | var C config 36 | 37 | // ReadConfigOption is a config option 38 | type ReadConfigOption struct { 39 | AppEnv string 40 | } 41 | 42 | // ReadConfig configures config file 43 | func ReadConfig(option ReadConfigOption) { 44 | Config := &C 45 | 46 | if environment.IsDev() { 47 | viper.AddConfigPath(filepath.Join(rootDir(), "config")) 48 | viper.SetConfigName("config") 49 | } else if environment.IsTest() || (option.AppEnv == environment.Test) { 50 | fmt.Println(rootDir()) 51 | viper.AddConfigPath(filepath.Join(rootDir(), "config")) 52 | viper.SetConfigName("config.test") 53 | } else if environment.IsE2E() || (option.AppEnv == environment.E2E) { 54 | fmt.Println(rootDir()) 55 | viper.AddConfigPath(filepath.Join(rootDir(), "config")) 56 | viper.SetConfigName("config.e2e") 57 | } else { 58 | // production configuration here 59 | fmt.Println("production configuration here") 60 | } 61 | 62 | viper.SetConfigType("yml") 63 | viper.AutomaticEnv() 64 | 65 | if err := viper.ReadInConfig(); err != nil { 66 | fmt.Println(err) 67 | log.Fatalln(err) 68 | } 69 | 70 | if err := viper.Unmarshal(&Config); err != nil { 71 | fmt.Println(err) 72 | os.Exit(1) 73 | } 74 | } 75 | 76 | func rootDir() string { 77 | _, b, _, _ := runtime.Caller(0) 78 | d := path.Join(path.Dir(b)) 79 | return filepath.Dir(d) 80 | } 81 | -------------------------------------------------------------------------------- /config/config.test.yml: -------------------------------------------------------------------------------- 1 | database: 2 | user: root 3 | password: root 4 | net: tcp 5 | addr: 127.0.0.1:3306 6 | dbname: golang_clean_architecture_ent_gqlgen 7 | allowNativePasswords: true 8 | params: 9 | parseTime: true 10 | charset: utf8mb4 11 | loc: Asia/Tokyo 12 | 13 | server: 14 | address: 8081 15 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | database: 2 | user: root 3 | password: root 4 | net: tcp 5 | addr: 127.0.0.1:3306 6 | dbname: golang_clean_architecture_ent_gqlgen 7 | allowNativePasswords: true 8 | params: 9 | parseTime: true 10 | charset: utf8mb4 11 | loc: Asia/Tokyo 12 | 13 | server: 14 | address: 8080 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mysql: 4 | platform: linux/x86_64 5 | image: mysql:8.0 6 | volumes: 7 | - mysql-data:/var/lib/mysql 8 | command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_ci --default-authentication-plugin=mysql_native_password 9 | environment: 10 | TZ: "/usr/share/zoneinfo/Asia/Tokyo" # wherever you want 11 | MYSQL_ROOT_PASSWORD: root 12 | 13 | ports: 14 | - '3306:3306' 15 | volumes: 16 | mysql-data: 17 | driver: local 18 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/reset_database.e2e.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS golang_clean_architecture_ent_gqlgen_e2e; 2 | CREATE DATABASE golang_clean_architecture_ent_gqlgen_e2e CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 3 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/reset_database.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS golang_clean_architecture_ent_gqlgen; 2 | CREATE DATABASE golang_clean_architecture_ent_gqlgen CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 3 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/reset_database.test.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS golang_clean_architecture_ent_gqlgen_test; 2 | CREATE DATABASE golang_clean_architecture_ent_gqlgen_test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 3 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/safe_updates.e2e.sql: -------------------------------------------------------------------------------- 1 | set global sql_safe_updates=0; 2 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/safe_updates.sql: -------------------------------------------------------------------------------- 1 | set global sql_safe_updates=1; 2 | -------------------------------------------------------------------------------- /docker/mysql_data/sql/safe_updates.test.sql: -------------------------------------------------------------------------------- 1 | set global sql_safe_updates=0; 2 | -------------------------------------------------------------------------------- /ent/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "log" 9 | 10 | "golang-clean-architecture-ent-gqlgen/ent/migrate" 11 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 12 | 13 | "golang-clean-architecture-ent-gqlgen/ent/todo" 14 | "golang-clean-architecture-ent-gqlgen/ent/user" 15 | 16 | "entgo.io/ent/dialect" 17 | "entgo.io/ent/dialect/sql" 18 | "entgo.io/ent/dialect/sql/sqlgraph" 19 | ) 20 | 21 | // Client is the client that holds all ent builders. 22 | type Client struct { 23 | config 24 | // Schema is the client for creating, migrating and dropping schema. 25 | Schema *migrate.Schema 26 | // Todo is the client for interacting with the Todo builders. 27 | Todo *TodoClient 28 | // User is the client for interacting with the User builders. 29 | User *UserClient 30 | } 31 | 32 | // NewClient creates a new client configured with the given options. 33 | func NewClient(opts ...Option) *Client { 34 | cfg := config{log: log.Println, hooks: &hooks{}} 35 | cfg.options(opts...) 36 | client := &Client{config: cfg} 37 | client.init() 38 | return client 39 | } 40 | 41 | func (c *Client) init() { 42 | c.Schema = migrate.NewSchema(c.driver) 43 | c.Todo = NewTodoClient(c.config) 44 | c.User = NewUserClient(c.config) 45 | } 46 | 47 | // Open opens a database/sql.DB specified by the driver name and 48 | // the data source name, and returns a new client attached to it. 49 | // Optional parameters can be added for configuring the client. 50 | func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { 51 | switch driverName { 52 | case dialect.MySQL, dialect.Postgres, dialect.SQLite: 53 | drv, err := sql.Open(driverName, dataSourceName) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return NewClient(append(options, Driver(drv))...), nil 58 | default: 59 | return nil, fmt.Errorf("unsupported driver: %q", driverName) 60 | } 61 | } 62 | 63 | // Tx returns a new transactional client. The provided context 64 | // is used until the transaction is committed or rolled back. 65 | func (c *Client) Tx(ctx context.Context) (*Tx, error) { 66 | if _, ok := c.driver.(*txDriver); ok { 67 | return nil, fmt.Errorf("ent: cannot start a transaction within a transaction") 68 | } 69 | tx, err := newTx(ctx, c.driver) 70 | if err != nil { 71 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 72 | } 73 | cfg := c.config 74 | cfg.driver = tx 75 | return &Tx{ 76 | ctx: ctx, 77 | config: cfg, 78 | Todo: NewTodoClient(cfg), 79 | User: NewUserClient(cfg), 80 | }, nil 81 | } 82 | 83 | // BeginTx returns a transactional client with specified options. 84 | func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { 85 | if _, ok := c.driver.(*txDriver); ok { 86 | return nil, fmt.Errorf("ent: cannot start a transaction within a transaction") 87 | } 88 | tx, err := c.driver.(interface { 89 | BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) 90 | }).BeginTx(ctx, opts) 91 | if err != nil { 92 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 93 | } 94 | cfg := c.config 95 | cfg.driver = &txDriver{tx: tx, drv: c.driver} 96 | return &Tx{ 97 | config: cfg, 98 | Todo: NewTodoClient(cfg), 99 | User: NewUserClient(cfg), 100 | }, nil 101 | } 102 | 103 | // Debug returns a new debug-client. It's used to get verbose logging on specific operations. 104 | // 105 | // client.Debug(). 106 | // Todo. 107 | // Query(). 108 | // Count(ctx) 109 | // 110 | func (c *Client) Debug() *Client { 111 | if c.debug { 112 | return c 113 | } 114 | cfg := c.config 115 | cfg.driver = dialect.Debug(c.driver, c.log) 116 | client := &Client{config: cfg} 117 | client.init() 118 | return client 119 | } 120 | 121 | // Close closes the database connection and prevents new queries from starting. 122 | func (c *Client) Close() error { 123 | return c.driver.Close() 124 | } 125 | 126 | // Use adds the mutation hooks to all the entity clients. 127 | // In order to add hooks to a specific client, call: `client.Node.Use(...)`. 128 | func (c *Client) Use(hooks ...Hook) { 129 | c.Todo.Use(hooks...) 130 | c.User.Use(hooks...) 131 | } 132 | 133 | // TodoClient is a client for the Todo schema. 134 | type TodoClient struct { 135 | config 136 | } 137 | 138 | // NewTodoClient returns a client for the Todo from the given config. 139 | func NewTodoClient(c config) *TodoClient { 140 | return &TodoClient{config: c} 141 | } 142 | 143 | // Use adds a list of mutation hooks to the hooks stack. 144 | // A call to `Use(f, g, h)` equals to `todo.Hooks(f(g(h())))`. 145 | func (c *TodoClient) Use(hooks ...Hook) { 146 | c.hooks.Todo = append(c.hooks.Todo, hooks...) 147 | } 148 | 149 | // Create returns a create builder for Todo. 150 | func (c *TodoClient) Create() *TodoCreate { 151 | mutation := newTodoMutation(c.config, OpCreate) 152 | return &TodoCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 153 | } 154 | 155 | // CreateBulk returns a builder for creating a bulk of Todo entities. 156 | func (c *TodoClient) CreateBulk(builders ...*TodoCreate) *TodoCreateBulk { 157 | return &TodoCreateBulk{config: c.config, builders: builders} 158 | } 159 | 160 | // Update returns an update builder for Todo. 161 | func (c *TodoClient) Update() *TodoUpdate { 162 | mutation := newTodoMutation(c.config, OpUpdate) 163 | return &TodoUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 164 | } 165 | 166 | // UpdateOne returns an update builder for the given entity. 167 | func (c *TodoClient) UpdateOne(t *Todo) *TodoUpdateOne { 168 | mutation := newTodoMutation(c.config, OpUpdateOne, withTodo(t)) 169 | return &TodoUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 170 | } 171 | 172 | // UpdateOneID returns an update builder for the given id. 173 | func (c *TodoClient) UpdateOneID(id ulid.ID) *TodoUpdateOne { 174 | mutation := newTodoMutation(c.config, OpUpdateOne, withTodoID(id)) 175 | return &TodoUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 176 | } 177 | 178 | // Delete returns a delete builder for Todo. 179 | func (c *TodoClient) Delete() *TodoDelete { 180 | mutation := newTodoMutation(c.config, OpDelete) 181 | return &TodoDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 182 | } 183 | 184 | // DeleteOne returns a delete builder for the given entity. 185 | func (c *TodoClient) DeleteOne(t *Todo) *TodoDeleteOne { 186 | return c.DeleteOneID(t.ID) 187 | } 188 | 189 | // DeleteOneID returns a delete builder for the given id. 190 | func (c *TodoClient) DeleteOneID(id ulid.ID) *TodoDeleteOne { 191 | builder := c.Delete().Where(todo.ID(id)) 192 | builder.mutation.id = &id 193 | builder.mutation.op = OpDeleteOne 194 | return &TodoDeleteOne{builder} 195 | } 196 | 197 | // Query returns a query builder for Todo. 198 | func (c *TodoClient) Query() *TodoQuery { 199 | return &TodoQuery{ 200 | config: c.config, 201 | } 202 | } 203 | 204 | // Get returns a Todo entity by its id. 205 | func (c *TodoClient) Get(ctx context.Context, id ulid.ID) (*Todo, error) { 206 | return c.Query().Where(todo.ID(id)).Only(ctx) 207 | } 208 | 209 | // GetX is like Get, but panics if an error occurs. 210 | func (c *TodoClient) GetX(ctx context.Context, id ulid.ID) *Todo { 211 | obj, err := c.Get(ctx, id) 212 | if err != nil { 213 | panic(err) 214 | } 215 | return obj 216 | } 217 | 218 | // QueryUser queries the user edge of a Todo. 219 | func (c *TodoClient) QueryUser(t *Todo) *UserQuery { 220 | query := &UserQuery{config: c.config} 221 | query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { 222 | id := t.ID 223 | step := sqlgraph.NewStep( 224 | sqlgraph.From(todo.Table, todo.FieldID, id), 225 | sqlgraph.To(user.Table, user.FieldID), 226 | sqlgraph.Edge(sqlgraph.M2O, true, todo.UserTable, todo.UserColumn), 227 | ) 228 | fromV = sqlgraph.Neighbors(t.driver.Dialect(), step) 229 | return fromV, nil 230 | } 231 | return query 232 | } 233 | 234 | // Hooks returns the client hooks. 235 | func (c *TodoClient) Hooks() []Hook { 236 | return c.hooks.Todo 237 | } 238 | 239 | // UserClient is a client for the User schema. 240 | type UserClient struct { 241 | config 242 | } 243 | 244 | // NewUserClient returns a client for the User from the given config. 245 | func NewUserClient(c config) *UserClient { 246 | return &UserClient{config: c} 247 | } 248 | 249 | // Use adds a list of mutation hooks to the hooks stack. 250 | // A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`. 251 | func (c *UserClient) Use(hooks ...Hook) { 252 | c.hooks.User = append(c.hooks.User, hooks...) 253 | } 254 | 255 | // Create returns a create builder for User. 256 | func (c *UserClient) Create() *UserCreate { 257 | mutation := newUserMutation(c.config, OpCreate) 258 | return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 259 | } 260 | 261 | // CreateBulk returns a builder for creating a bulk of User entities. 262 | func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { 263 | return &UserCreateBulk{config: c.config, builders: builders} 264 | } 265 | 266 | // Update returns an update builder for User. 267 | func (c *UserClient) Update() *UserUpdate { 268 | mutation := newUserMutation(c.config, OpUpdate) 269 | return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 270 | } 271 | 272 | // UpdateOne returns an update builder for the given entity. 273 | func (c *UserClient) UpdateOne(u *User) *UserUpdateOne { 274 | mutation := newUserMutation(c.config, OpUpdateOne, withUser(u)) 275 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 276 | } 277 | 278 | // UpdateOneID returns an update builder for the given id. 279 | func (c *UserClient) UpdateOneID(id ulid.ID) *UserUpdateOne { 280 | mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id)) 281 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 282 | } 283 | 284 | // Delete returns a delete builder for User. 285 | func (c *UserClient) Delete() *UserDelete { 286 | mutation := newUserMutation(c.config, OpDelete) 287 | return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 288 | } 289 | 290 | // DeleteOne returns a delete builder for the given entity. 291 | func (c *UserClient) DeleteOne(u *User) *UserDeleteOne { 292 | return c.DeleteOneID(u.ID) 293 | } 294 | 295 | // DeleteOneID returns a delete builder for the given id. 296 | func (c *UserClient) DeleteOneID(id ulid.ID) *UserDeleteOne { 297 | builder := c.Delete().Where(user.ID(id)) 298 | builder.mutation.id = &id 299 | builder.mutation.op = OpDeleteOne 300 | return &UserDeleteOne{builder} 301 | } 302 | 303 | // Query returns a query builder for User. 304 | func (c *UserClient) Query() *UserQuery { 305 | return &UserQuery{ 306 | config: c.config, 307 | } 308 | } 309 | 310 | // Get returns a User entity by its id. 311 | func (c *UserClient) Get(ctx context.Context, id ulid.ID) (*User, error) { 312 | return c.Query().Where(user.ID(id)).Only(ctx) 313 | } 314 | 315 | // GetX is like Get, but panics if an error occurs. 316 | func (c *UserClient) GetX(ctx context.Context, id ulid.ID) *User { 317 | obj, err := c.Get(ctx, id) 318 | if err != nil { 319 | panic(err) 320 | } 321 | return obj 322 | } 323 | 324 | // QueryTodos queries the todos edge of a User. 325 | func (c *UserClient) QueryTodos(u *User) *TodoQuery { 326 | query := &TodoQuery{config: c.config} 327 | query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) { 328 | id := u.ID 329 | step := sqlgraph.NewStep( 330 | sqlgraph.From(user.Table, user.FieldID, id), 331 | sqlgraph.To(todo.Table, todo.FieldID), 332 | sqlgraph.Edge(sqlgraph.O2M, false, user.TodosTable, user.TodosColumn), 333 | ) 334 | fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) 335 | return fromV, nil 336 | } 337 | return query 338 | } 339 | 340 | // Hooks returns the client hooks. 341 | func (c *UserClient) Hooks() []Hook { 342 | return c.hooks.User 343 | } 344 | -------------------------------------------------------------------------------- /ent/config.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "entgo.io/ent" 7 | "entgo.io/ent/dialect" 8 | ) 9 | 10 | // Option function to configure the client. 11 | type Option func(*config) 12 | 13 | // Config is the configuration for the client and its builder. 14 | type config struct { 15 | // driver used for executing database requests. 16 | driver dialect.Driver 17 | // debug enable a debug logging. 18 | debug bool 19 | // log used for logging on debug mode. 20 | log func(...interface{}) 21 | // hooks to execute on mutations. 22 | hooks *hooks 23 | } 24 | 25 | // hooks per client, for fast access. 26 | type hooks struct { 27 | Todo []ent.Hook 28 | User []ent.Hook 29 | } 30 | 31 | // Options applies the options on the config object. 32 | func (c *config) options(opts ...Option) { 33 | for _, opt := range opts { 34 | opt(c) 35 | } 36 | if c.debug { 37 | c.driver = dialect.Debug(c.driver, c.log) 38 | } 39 | } 40 | 41 | // Debug enables debug logging on the ent.Driver. 42 | func Debug() Option { 43 | return func(c *config) { 44 | c.debug = true 45 | } 46 | } 47 | 48 | // Log sets the logging function for debug mode. 49 | func Log(fn func(...interface{})) Option { 50 | return func(c *config) { 51 | c.log = fn 52 | } 53 | } 54 | 55 | // Driver configures the client driver. 56 | func Driver(driver dialect.Driver) Option { 57 | return func(c *config) { 58 | c.driver = driver 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ent/context.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | type clientCtxKey struct{} 10 | 11 | // FromContext returns a Client stored inside a context, or nil if there isn't one. 12 | func FromContext(ctx context.Context) *Client { 13 | c, _ := ctx.Value(clientCtxKey{}).(*Client) 14 | return c 15 | } 16 | 17 | // NewContext returns a new context with the given Client attached. 18 | func NewContext(parent context.Context, c *Client) context.Context { 19 | return context.WithValue(parent, clientCtxKey{}, c) 20 | } 21 | 22 | type txCtxKey struct{} 23 | 24 | // TxFromContext returns a Tx stored inside a context, or nil if there isn't one. 25 | func TxFromContext(ctx context.Context) *Tx { 26 | tx, _ := ctx.Value(txCtxKey{}).(*Tx) 27 | return tx 28 | } 29 | 30 | // NewTxContext returns a new context with the given Tx attached. 31 | func NewTxContext(parent context.Context, tx *Tx) context.Context { 32 | return context.WithValue(parent, txCtxKey{}, tx) 33 | } 34 | -------------------------------------------------------------------------------- /ent/ent.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "golang-clean-architecture-ent-gqlgen/ent/todo" 9 | "golang-clean-architecture-ent-gqlgen/ent/user" 10 | 11 | "entgo.io/ent" 12 | "entgo.io/ent/dialect/sql" 13 | ) 14 | 15 | // ent aliases to avoid import conflicts in user's code. 16 | type ( 17 | Op = ent.Op 18 | Hook = ent.Hook 19 | Value = ent.Value 20 | Query = ent.Query 21 | Policy = ent.Policy 22 | Mutator = ent.Mutator 23 | Mutation = ent.Mutation 24 | MutateFunc = ent.MutateFunc 25 | ) 26 | 27 | // OrderFunc applies an ordering on the sql selector. 28 | type OrderFunc func(*sql.Selector) 29 | 30 | // columnChecker returns a function indicates if the column exists in the given column. 31 | func columnChecker(table string) func(string) error { 32 | checks := map[string]func(string) bool{ 33 | todo.Table: todo.ValidColumn, 34 | user.Table: user.ValidColumn, 35 | } 36 | check, ok := checks[table] 37 | if !ok { 38 | return func(string) error { 39 | return fmt.Errorf("unknown table %q", table) 40 | } 41 | } 42 | return func(column string) error { 43 | if !check(column) { 44 | return fmt.Errorf("unknown column %q for table %q", column, table) 45 | } 46 | return nil 47 | } 48 | } 49 | 50 | // Asc applies the given fields in ASC order. 51 | func Asc(fields ...string) OrderFunc { 52 | return func(s *sql.Selector) { 53 | check := columnChecker(s.TableName()) 54 | for _, f := range fields { 55 | if err := check(f); err != nil { 56 | s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) 57 | } 58 | s.OrderBy(sql.Asc(s.C(f))) 59 | } 60 | } 61 | } 62 | 63 | // Desc applies the given fields in DESC order. 64 | func Desc(fields ...string) OrderFunc { 65 | return func(s *sql.Selector) { 66 | check := columnChecker(s.TableName()) 67 | for _, f := range fields { 68 | if err := check(f); err != nil { 69 | s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) 70 | } 71 | s.OrderBy(sql.Desc(s.C(f))) 72 | } 73 | } 74 | } 75 | 76 | // AggregateFunc applies an aggregation step on the group-by traversal/selector. 77 | type AggregateFunc func(*sql.Selector) string 78 | 79 | // As is a pseudo aggregation function for renaming another other functions with custom names. For example: 80 | // 81 | // GroupBy(field1, field2). 82 | // Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). 83 | // Scan(ctx, &v) 84 | // 85 | func As(fn AggregateFunc, end string) AggregateFunc { 86 | return func(s *sql.Selector) string { 87 | return sql.As(fn(s), end) 88 | } 89 | } 90 | 91 | // Count applies the "count" aggregation function on each group. 92 | func Count() AggregateFunc { 93 | return func(s *sql.Selector) string { 94 | return sql.Count("*") 95 | } 96 | } 97 | 98 | // Max applies the "max" aggregation function on the given field of each group. 99 | func Max(field string) AggregateFunc { 100 | return func(s *sql.Selector) string { 101 | check := columnChecker(s.TableName()) 102 | if err := check(field); err != nil { 103 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 104 | return "" 105 | } 106 | return sql.Max(s.C(field)) 107 | } 108 | } 109 | 110 | // Mean applies the "mean" aggregation function on the given field of each group. 111 | func Mean(field string) AggregateFunc { 112 | return func(s *sql.Selector) string { 113 | check := columnChecker(s.TableName()) 114 | if err := check(field); err != nil { 115 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 116 | return "" 117 | } 118 | return sql.Avg(s.C(field)) 119 | } 120 | } 121 | 122 | // Min applies the "min" aggregation function on the given field of each group. 123 | func Min(field string) AggregateFunc { 124 | return func(s *sql.Selector) string { 125 | check := columnChecker(s.TableName()) 126 | if err := check(field); err != nil { 127 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 128 | return "" 129 | } 130 | return sql.Min(s.C(field)) 131 | } 132 | } 133 | 134 | // Sum applies the "sum" aggregation function on the given field of each group. 135 | func Sum(field string) AggregateFunc { 136 | return func(s *sql.Selector) string { 137 | check := columnChecker(s.TableName()) 138 | if err := check(field); err != nil { 139 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 140 | return "" 141 | } 142 | return sql.Sum(s.C(field)) 143 | } 144 | } 145 | 146 | // ValidationError returns when validating a field fails. 147 | type ValidationError struct { 148 | Name string // Field or edge name. 149 | err error 150 | } 151 | 152 | // Error implements the error interface. 153 | func (e *ValidationError) Error() string { 154 | return e.err.Error() 155 | } 156 | 157 | // Unwrap implements the errors.Wrapper interface. 158 | func (e *ValidationError) Unwrap() error { 159 | return e.err 160 | } 161 | 162 | // IsValidationError returns a boolean indicating whether the error is a validation error. 163 | func IsValidationError(err error) bool { 164 | if err == nil { 165 | return false 166 | } 167 | var e *ValidationError 168 | return errors.As(err, &e) 169 | } 170 | 171 | // NotFoundError returns when trying to fetch a specific entity and it was not found in the database. 172 | type NotFoundError struct { 173 | label string 174 | } 175 | 176 | // Error implements the error interface. 177 | func (e *NotFoundError) Error() string { 178 | return "ent: " + e.label + " not found" 179 | } 180 | 181 | // IsNotFound returns a boolean indicating whether the error is a not found error. 182 | func IsNotFound(err error) bool { 183 | if err == nil { 184 | return false 185 | } 186 | var e *NotFoundError 187 | return errors.As(err, &e) 188 | } 189 | 190 | // MaskNotFound masks not found error. 191 | func MaskNotFound(err error) error { 192 | if IsNotFound(err) { 193 | return nil 194 | } 195 | return err 196 | } 197 | 198 | // NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. 199 | type NotSingularError struct { 200 | label string 201 | } 202 | 203 | // Error implements the error interface. 204 | func (e *NotSingularError) Error() string { 205 | return "ent: " + e.label + " not singular" 206 | } 207 | 208 | // IsNotSingular returns a boolean indicating whether the error is a not singular error. 209 | func IsNotSingular(err error) bool { 210 | if err == nil { 211 | return false 212 | } 213 | var e *NotSingularError 214 | return errors.As(err, &e) 215 | } 216 | 217 | // NotLoadedError returns when trying to get a node that was not loaded by the query. 218 | type NotLoadedError struct { 219 | edge string 220 | } 221 | 222 | // Error implements the error interface. 223 | func (e *NotLoadedError) Error() string { 224 | return "ent: " + e.edge + " edge was not loaded" 225 | } 226 | 227 | // IsNotLoaded returns a boolean indicating whether the error is a not loaded error. 228 | func IsNotLoaded(err error) bool { 229 | if err == nil { 230 | return false 231 | } 232 | var e *NotLoadedError 233 | return errors.As(err, &e) 234 | } 235 | 236 | // ConstraintError returns when trying to create/update one or more entities and 237 | // one or more of their constraints failed. For example, violation of edge or 238 | // field uniqueness. 239 | type ConstraintError struct { 240 | msg string 241 | wrap error 242 | } 243 | 244 | // Error implements the error interface. 245 | func (e ConstraintError) Error() string { 246 | return "ent: constraint failed: " + e.msg 247 | } 248 | 249 | // Unwrap implements the errors.Wrapper interface. 250 | func (e *ConstraintError) Unwrap() error { 251 | return e.wrap 252 | } 253 | 254 | // IsConstraintError returns a boolean indicating whether the error is a constraint failure. 255 | func IsConstraintError(err error) bool { 256 | if err == nil { 257 | return false 258 | } 259 | var e *ConstraintError 260 | return errors.As(err, &e) 261 | } 262 | -------------------------------------------------------------------------------- /ent/entc.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "log" 8 | 9 | "entgo.io/contrib/entgql" 10 | "entgo.io/ent/entc" 11 | "entgo.io/ent/entc/gen" 12 | ) 13 | 14 | func main() { 15 | ex, err := entgql.NewExtension( 16 | entgql.WithWhereFilters(true), 17 | entgql.WithConfigPath("../gqlgen.yml"), 18 | entgql.WithSchemaPath("../graph/ent.graphqls"), 19 | ) 20 | if err != nil { 21 | log.Fatalf("Error: failed creating entgql extension: %v", err) 22 | } 23 | 24 | opts := []entc.Option{ 25 | entc.Extensions(ex), 26 | entc.TemplateDir("./template"), 27 | } 28 | 29 | if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil { 30 | log.Fatalf("Error: failed running ent codegen: %v", err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | "golang-clean-architecture-ent-gqlgen/ent" 8 | // required by schema hooks. 9 | _ "golang-clean-architecture-ent-gqlgen/ent/runtime" 10 | 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | type ( 15 | // TestingT is the interface that is shared between 16 | // testing.T and testing.B and used by enttest. 17 | TestingT interface { 18 | FailNow() 19 | Error(...interface{}) 20 | } 21 | 22 | // Option configures client creation. 23 | Option func(*options) 24 | 25 | options struct { 26 | opts []ent.Option 27 | migrateOpts []schema.MigrateOption 28 | } 29 | ) 30 | 31 | // WithOptions forwards options to client creation. 32 | func WithOptions(opts ...ent.Option) Option { 33 | return func(o *options) { 34 | o.opts = append(o.opts, opts...) 35 | } 36 | } 37 | 38 | // WithMigrateOptions forwards options to auto migration. 39 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 40 | return func(o *options) { 41 | o.migrateOpts = append(o.migrateOpts, opts...) 42 | } 43 | } 44 | 45 | func newOptions(opts []Option) *options { 46 | o := &options{} 47 | for _, opt := range opts { 48 | opt(o) 49 | } 50 | return o 51 | } 52 | 53 | // Open calls ent.Open and auto-run migration. 54 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 55 | o := newOptions(opts) 56 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 57 | if err != nil { 58 | t.Error(err) 59 | t.FailNow() 60 | } 61 | if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil { 62 | t.Error(err) 63 | t.FailNow() 64 | } 65 | return c 66 | } 67 | 68 | // NewClient calls ent.NewClient and auto-run migration. 69 | func NewClient(t TestingT, opts ...Option) *ent.Client { 70 | o := newOptions(opts) 71 | c := ent.NewClient(o.opts...) 72 | if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil { 73 | t.Error(err) 74 | t.FailNow() 75 | } 76 | return c 77 | } 78 | -------------------------------------------------------------------------------- /ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | //go:generate go run -mod=mod entc.go 4 | -------------------------------------------------------------------------------- /ent/gql_collection.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/99designs/gqlgen/graphql" 9 | ) 10 | 11 | // CollectFields tells the query-builder to eagerly load connected nodes by resolver context. 12 | func (t *TodoQuery) CollectFields(ctx context.Context, satisfies ...string) *TodoQuery { 13 | if fc := graphql.GetFieldContext(ctx); fc != nil { 14 | t = t.collectField(graphql.GetOperationContext(ctx), fc.Field, satisfies...) 15 | } 16 | return t 17 | } 18 | 19 | func (t *TodoQuery) collectField(ctx *graphql.OperationContext, field graphql.CollectedField, satisfies ...string) *TodoQuery { 20 | return t 21 | } 22 | 23 | // CollectFields tells the query-builder to eagerly load connected nodes by resolver context. 24 | func (u *UserQuery) CollectFields(ctx context.Context, satisfies ...string) *UserQuery { 25 | if fc := graphql.GetFieldContext(ctx); fc != nil { 26 | u = u.collectField(graphql.GetOperationContext(ctx), fc.Field, satisfies...) 27 | } 28 | return u 29 | } 30 | 31 | func (u *UserQuery) collectField(ctx *graphql.OperationContext, field graphql.CollectedField, satisfies ...string) *UserQuery { 32 | return u 33 | } 34 | -------------------------------------------------------------------------------- /ent/gql_edge.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import "context" 6 | 7 | func (t *Todo) User(ctx context.Context) (*User, error) { 8 | result, err := t.Edges.UserOrErr() 9 | if IsNotLoaded(err) { 10 | result, err = t.QueryUser().Only(ctx) 11 | } 12 | return result, MaskNotFound(err) 13 | } 14 | 15 | func (u *User) Todos(ctx context.Context) ([]*Todo, error) { 16 | result, err := u.Edges.TodosOrErr() 17 | if IsNotLoaded(err) { 18 | result, err = u.QueryTodos().All(ctx) 19 | } 20 | return result, err 21 | } 22 | -------------------------------------------------------------------------------- /ent/gql_node.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/ent/todo" 11 | "golang-clean-architecture-ent-gqlgen/ent/user" 12 | 13 | "entgo.io/contrib/entgql" 14 | "github.com/99designs/gqlgen/graphql" 15 | "github.com/hashicorp/go-multierror" 16 | ) 17 | 18 | // Noder wraps the basic Node method. 19 | type Noder interface { 20 | Node(context.Context) (*Node, error) 21 | } 22 | 23 | // Node in the graph. 24 | type Node struct { 25 | ID ulid.ID `json:"id,omitempty"` // node id. 26 | Type string `json:"type,omitempty"` // node type. 27 | Fields []*Field `json:"fields,omitempty"` // node fields. 28 | Edges []*Edge `json:"edges,omitempty"` // node edges. 29 | } 30 | 31 | // Field of a node. 32 | type Field struct { 33 | Type string `json:"type,omitempty"` // field type. 34 | Name string `json:"name,omitempty"` // field name (as in struct). 35 | Value string `json:"value,omitempty"` // stringified value. 36 | } 37 | 38 | // Edges between two nodes. 39 | type Edge struct { 40 | Type string `json:"type,omitempty"` // edge type. 41 | Name string `json:"name,omitempty"` // edge name. 42 | IDs []ulid.ID `json:"ids,omitempty"` // node ids (where this edge point to). 43 | } 44 | 45 | func (t *Todo) Node(ctx context.Context) (node *Node, err error) { 46 | node = &Node{ 47 | ID: t.ID, 48 | Type: "Todo", 49 | Fields: make([]*Field, 6), 50 | Edges: make([]*Edge, 1), 51 | } 52 | var buf []byte 53 | if buf, err = json.Marshal(t.UserID); err != nil { 54 | return nil, err 55 | } 56 | node.Fields[0] = &Field{ 57 | Type: "ulid.ID", 58 | Name: "user_id", 59 | Value: string(buf), 60 | } 61 | if buf, err = json.Marshal(t.Name); err != nil { 62 | return nil, err 63 | } 64 | node.Fields[1] = &Field{ 65 | Type: "string", 66 | Name: "name", 67 | Value: string(buf), 68 | } 69 | if buf, err = json.Marshal(t.Status); err != nil { 70 | return nil, err 71 | } 72 | node.Fields[2] = &Field{ 73 | Type: "todo.Status", 74 | Name: "status", 75 | Value: string(buf), 76 | } 77 | if buf, err = json.Marshal(t.Priority); err != nil { 78 | return nil, err 79 | } 80 | node.Fields[3] = &Field{ 81 | Type: "int", 82 | Name: "priority", 83 | Value: string(buf), 84 | } 85 | if buf, err = json.Marshal(t.CreatedAt); err != nil { 86 | return nil, err 87 | } 88 | node.Fields[4] = &Field{ 89 | Type: "time.Time", 90 | Name: "created_at", 91 | Value: string(buf), 92 | } 93 | if buf, err = json.Marshal(t.UpdatedAt); err != nil { 94 | return nil, err 95 | } 96 | node.Fields[5] = &Field{ 97 | Type: "time.Time", 98 | Name: "updated_at", 99 | Value: string(buf), 100 | } 101 | node.Edges[0] = &Edge{ 102 | Type: "User", 103 | Name: "user", 104 | } 105 | err = t.QueryUser(). 106 | Select(user.FieldID). 107 | Scan(ctx, &node.Edges[0].IDs) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return node, nil 112 | } 113 | 114 | func (u *User) Node(ctx context.Context) (node *Node, err error) { 115 | node = &Node{ 116 | ID: u.ID, 117 | Type: "User", 118 | Fields: make([]*Field, 4), 119 | Edges: make([]*Edge, 1), 120 | } 121 | var buf []byte 122 | if buf, err = json.Marshal(u.Name); err != nil { 123 | return nil, err 124 | } 125 | node.Fields[0] = &Field{ 126 | Type: "string", 127 | Name: "name", 128 | Value: string(buf), 129 | } 130 | if buf, err = json.Marshal(u.Age); err != nil { 131 | return nil, err 132 | } 133 | node.Fields[1] = &Field{ 134 | Type: "int", 135 | Name: "age", 136 | Value: string(buf), 137 | } 138 | if buf, err = json.Marshal(u.CreatedAt); err != nil { 139 | return nil, err 140 | } 141 | node.Fields[2] = &Field{ 142 | Type: "time.Time", 143 | Name: "created_at", 144 | Value: string(buf), 145 | } 146 | if buf, err = json.Marshal(u.UpdatedAt); err != nil { 147 | return nil, err 148 | } 149 | node.Fields[3] = &Field{ 150 | Type: "time.Time", 151 | Name: "updated_at", 152 | Value: string(buf), 153 | } 154 | node.Edges[0] = &Edge{ 155 | Type: "Todo", 156 | Name: "todos", 157 | } 158 | err = u.QueryTodos(). 159 | Select(todo.FieldID). 160 | Scan(ctx, &node.Edges[0].IDs) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return node, nil 165 | } 166 | 167 | func (c *Client) Node(ctx context.Context, id ulid.ID) (*Node, error) { 168 | n, err := c.Noder(ctx, id) 169 | if err != nil { 170 | return nil, err 171 | } 172 | return n.Node(ctx) 173 | } 174 | 175 | var errNodeInvalidID = &NotFoundError{"node"} 176 | 177 | // NodeOption allows configuring the Noder execution using functional options. 178 | type NodeOption func(*nodeOptions) 179 | 180 | // WithNodeType sets the node Type resolver function (i.e. the table to query). 181 | // If was not provided, the table will be derived from the universal-id 182 | // configuration as described in: https://entgo.io/docs/migrate/#universal-ids. 183 | func WithNodeType(f func(context.Context, ulid.ID) (string, error)) NodeOption { 184 | return func(o *nodeOptions) { 185 | o.nodeType = f 186 | } 187 | } 188 | 189 | // WithFixedNodeType sets the Type of the node to a fixed value. 190 | func WithFixedNodeType(t string) NodeOption { 191 | return WithNodeType(func(context.Context, ulid.ID) (string, error) { 192 | return t, nil 193 | }) 194 | } 195 | 196 | type nodeOptions struct { 197 | nodeType func(context.Context, ulid.ID) (string, error) 198 | } 199 | 200 | func (c *Client) newNodeOpts(opts []NodeOption) *nodeOptions { 201 | nopts := &nodeOptions{} 202 | for _, opt := range opts { 203 | opt(nopts) 204 | } 205 | if nopts.nodeType == nil { 206 | nopts.nodeType = func(ctx context.Context, id ulid.ID) (string, error) { 207 | return "", fmt.Errorf("cannot resolve noder (%v) without its type", id) 208 | } 209 | } 210 | return nopts 211 | } 212 | 213 | // Noder returns a Node by its id. If the NodeType was not provided, it will 214 | // be derived from the id value according to the universal-id configuration. 215 | // 216 | // c.Noder(ctx, id) 217 | // c.Noder(ctx, id, ent.WithNodeType(pet.Table)) 218 | // 219 | func (c *Client) Noder(ctx context.Context, id ulid.ID, opts ...NodeOption) (_ Noder, err error) { 220 | defer func() { 221 | if IsNotFound(err) { 222 | err = multierror.Append(err, entgql.ErrNodeNotFound(id)) 223 | } 224 | }() 225 | table, err := c.newNodeOpts(opts).nodeType(ctx, id) 226 | if err != nil { 227 | return nil, err 228 | } 229 | return c.noder(ctx, table, id) 230 | } 231 | 232 | func (c *Client) noder(ctx context.Context, table string, id ulid.ID) (Noder, error) { 233 | switch table { 234 | case todo.Table: 235 | n, err := c.Todo.Query(). 236 | Where(todo.ID(id)). 237 | CollectFields(ctx, "Todo"). 238 | Only(ctx) 239 | if err != nil { 240 | return nil, err 241 | } 242 | return n, nil 243 | case user.Table: 244 | n, err := c.User.Query(). 245 | Where(user.ID(id)). 246 | CollectFields(ctx, "User"). 247 | Only(ctx) 248 | if err != nil { 249 | return nil, err 250 | } 251 | return n, nil 252 | default: 253 | return nil, fmt.Errorf("cannot resolve noder from table %q: %w", table, errNodeInvalidID) 254 | } 255 | } 256 | 257 | func (c *Client) Noders(ctx context.Context, ids []ulid.ID, opts ...NodeOption) ([]Noder, error) { 258 | switch len(ids) { 259 | case 1: 260 | noder, err := c.Noder(ctx, ids[0], opts...) 261 | if err != nil { 262 | return nil, err 263 | } 264 | return []Noder{noder}, nil 265 | case 0: 266 | return []Noder{}, nil 267 | } 268 | 269 | noders := make([]Noder, len(ids)) 270 | errors := make([]error, len(ids)) 271 | tables := make(map[string][]ulid.ID) 272 | id2idx := make(map[ulid.ID][]int, len(ids)) 273 | nopts := c.newNodeOpts(opts) 274 | for i, id := range ids { 275 | table, err := nopts.nodeType(ctx, id) 276 | if err != nil { 277 | errors[i] = err 278 | continue 279 | } 280 | tables[table] = append(tables[table], id) 281 | id2idx[id] = append(id2idx[id], i) 282 | } 283 | 284 | for table, ids := range tables { 285 | nodes, err := c.noders(ctx, table, ids) 286 | if err != nil { 287 | for _, id := range ids { 288 | for _, idx := range id2idx[id] { 289 | errors[idx] = err 290 | } 291 | } 292 | } else { 293 | for i, id := range ids { 294 | for _, idx := range id2idx[id] { 295 | noders[idx] = nodes[i] 296 | } 297 | } 298 | } 299 | } 300 | 301 | for i, id := range ids { 302 | if errors[i] == nil { 303 | if noders[i] != nil { 304 | continue 305 | } 306 | errors[i] = entgql.ErrNodeNotFound(id) 307 | } else if IsNotFound(errors[i]) { 308 | errors[i] = multierror.Append(errors[i], entgql.ErrNodeNotFound(id)) 309 | } 310 | ctx := graphql.WithPathContext(ctx, 311 | graphql.NewPathWithIndex(i), 312 | ) 313 | graphql.AddError(ctx, errors[i]) 314 | } 315 | return noders, nil 316 | } 317 | 318 | func (c *Client) noders(ctx context.Context, table string, ids []ulid.ID) ([]Noder, error) { 319 | noders := make([]Noder, len(ids)) 320 | idmap := make(map[ulid.ID][]*Noder, len(ids)) 321 | for i, id := range ids { 322 | idmap[id] = append(idmap[id], &noders[i]) 323 | } 324 | switch table { 325 | case todo.Table: 326 | nodes, err := c.Todo.Query(). 327 | Where(todo.IDIn(ids...)). 328 | CollectFields(ctx, "Todo"). 329 | All(ctx) 330 | if err != nil { 331 | return nil, err 332 | } 333 | for _, node := range nodes { 334 | for _, noder := range idmap[node.ID] { 335 | *noder = node 336 | } 337 | } 338 | case user.Table: 339 | nodes, err := c.User.Query(). 340 | Where(user.IDIn(ids...)). 341 | CollectFields(ctx, "User"). 342 | All(ctx) 343 | if err != nil { 344 | return nil, err 345 | } 346 | for _, node := range nodes { 347 | for _, noder := range idmap[node.ID] { 348 | *noder = node 349 | } 350 | } 351 | default: 352 | return nil, fmt.Errorf("cannot resolve noders from table %q: %w", table, errNodeInvalidID) 353 | } 354 | return noders, nil 355 | } 356 | -------------------------------------------------------------------------------- /ent/gql_transaction.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "database/sql/driver" 8 | "errors" 9 | ) 10 | 11 | // OpenTx opens a transaction and returns a transactional 12 | // context along with the created transaction. 13 | func (c *Client) OpenTx(ctx context.Context) (context.Context, driver.Tx, error) { 14 | tx, err := c.Tx(ctx) 15 | if err != nil { 16 | return nil, nil, err 17 | } 18 | ctx = NewTxContext(ctx, tx) 19 | ctx = NewContext(ctx, tx.Client()) 20 | return ctx, tx, nil 21 | } 22 | 23 | // OpenTxFromContext open transactions from client stored in context. 24 | func OpenTxFromContext(ctx context.Context) (context.Context, driver.Tx, error) { 25 | client := FromContext(ctx) 26 | if client == nil { 27 | return nil, nil, errors.New("no client attached to context") 28 | } 29 | return client.OpenTx(ctx) 30 | } 31 | -------------------------------------------------------------------------------- /ent/hook/hook.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package hook 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "golang-clean-architecture-ent-gqlgen/ent" 9 | ) 10 | 11 | // The TodoFunc type is an adapter to allow the use of ordinary 12 | // function as Todo mutator. 13 | type TodoFunc func(context.Context, *ent.TodoMutation) (ent.Value, error) 14 | 15 | // Mutate calls f(ctx, m). 16 | func (f TodoFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 17 | mv, ok := m.(*ent.TodoMutation) 18 | if !ok { 19 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TodoMutation", m) 20 | } 21 | return f(ctx, mv) 22 | } 23 | 24 | // The UserFunc type is an adapter to allow the use of ordinary 25 | // function as User mutator. 26 | type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) 27 | 28 | // Mutate calls f(ctx, m). 29 | func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 30 | mv, ok := m.(*ent.UserMutation) 31 | if !ok { 32 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m) 33 | } 34 | return f(ctx, mv) 35 | } 36 | 37 | // Condition is a hook condition function. 38 | type Condition func(context.Context, ent.Mutation) bool 39 | 40 | // And groups conditions with the AND operator. 41 | func And(first, second Condition, rest ...Condition) Condition { 42 | return func(ctx context.Context, m ent.Mutation) bool { 43 | if !first(ctx, m) || !second(ctx, m) { 44 | return false 45 | } 46 | for _, cond := range rest { 47 | if !cond(ctx, m) { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | } 54 | 55 | // Or groups conditions with the OR operator. 56 | func Or(first, second Condition, rest ...Condition) Condition { 57 | return func(ctx context.Context, m ent.Mutation) bool { 58 | if first(ctx, m) || second(ctx, m) { 59 | return true 60 | } 61 | for _, cond := range rest { 62 | if cond(ctx, m) { 63 | return true 64 | } 65 | } 66 | return false 67 | } 68 | } 69 | 70 | // Not negates a given condition. 71 | func Not(cond Condition) Condition { 72 | return func(ctx context.Context, m ent.Mutation) bool { 73 | return !cond(ctx, m) 74 | } 75 | } 76 | 77 | // HasOp is a condition testing mutation operation. 78 | func HasOp(op ent.Op) Condition { 79 | return func(_ context.Context, m ent.Mutation) bool { 80 | return m.Op().Is(op) 81 | } 82 | } 83 | 84 | // HasAddedFields is a condition validating `.AddedField` on fields. 85 | func HasAddedFields(field string, fields ...string) Condition { 86 | return func(_ context.Context, m ent.Mutation) bool { 87 | if _, exists := m.AddedField(field); !exists { 88 | return false 89 | } 90 | for _, field := range fields { 91 | if _, exists := m.AddedField(field); !exists { 92 | return false 93 | } 94 | } 95 | return true 96 | } 97 | } 98 | 99 | // HasClearedFields is a condition validating `.FieldCleared` on fields. 100 | func HasClearedFields(field string, fields ...string) Condition { 101 | return func(_ context.Context, m ent.Mutation) bool { 102 | if exists := m.FieldCleared(field); !exists { 103 | return false 104 | } 105 | for _, field := range fields { 106 | if exists := m.FieldCleared(field); !exists { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | } 113 | 114 | // HasFields is a condition validating `.Field` on fields. 115 | func HasFields(field string, fields ...string) Condition { 116 | return func(_ context.Context, m ent.Mutation) bool { 117 | if _, exists := m.Field(field); !exists { 118 | return false 119 | } 120 | for _, field := range fields { 121 | if _, exists := m.Field(field); !exists { 122 | return false 123 | } 124 | } 125 | return true 126 | } 127 | } 128 | 129 | // If executes the given hook under condition. 130 | // 131 | // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) 132 | // 133 | func If(hk ent.Hook, cond Condition) ent.Hook { 134 | return func(next ent.Mutator) ent.Mutator { 135 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 136 | if cond(ctx, m) { 137 | return hk(next).Mutate(ctx, m) 138 | } 139 | return next.Mutate(ctx, m) 140 | }) 141 | } 142 | } 143 | 144 | // On executes the given hook only for the given operation. 145 | // 146 | // hook.On(Log, ent.Delete|ent.Create) 147 | // 148 | func On(hk ent.Hook, op ent.Op) ent.Hook { 149 | return If(hk, HasOp(op)) 150 | } 151 | 152 | // Unless skips the given hook only for the given operation. 153 | // 154 | // hook.Unless(Log, ent.Update|ent.UpdateOne) 155 | // 156 | func Unless(hk ent.Hook, op ent.Op) ent.Hook { 157 | return If(hk, Not(HasOp(op))) 158 | } 159 | 160 | // FixedError is a hook returning a fixed error. 161 | func FixedError(err error) ent.Hook { 162 | return func(ent.Mutator) ent.Mutator { 163 | return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { 164 | return nil, err 165 | }) 166 | } 167 | } 168 | 169 | // Reject returns a hook that rejects all operations that match op. 170 | // 171 | // func (T) Hooks() []ent.Hook { 172 | // return []ent.Hook{ 173 | // Reject(ent.Delete|ent.Update), 174 | // } 175 | // } 176 | // 177 | func Reject(op ent.Op) ent.Hook { 178 | hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) 179 | return On(hk, op) 180 | } 181 | 182 | // Chain acts as a list of hooks and is effectively immutable. 183 | // Once created, it will always hold the same set of hooks in the same order. 184 | type Chain struct { 185 | hooks []ent.Hook 186 | } 187 | 188 | // NewChain creates a new chain of hooks. 189 | func NewChain(hooks ...ent.Hook) Chain { 190 | return Chain{append([]ent.Hook(nil), hooks...)} 191 | } 192 | 193 | // Hook chains the list of hooks and returns the final hook. 194 | func (c Chain) Hook() ent.Hook { 195 | return func(mutator ent.Mutator) ent.Mutator { 196 | for i := len(c.hooks) - 1; i >= 0; i-- { 197 | mutator = c.hooks[i](mutator) 198 | } 199 | return mutator 200 | } 201 | } 202 | 203 | // Append extends a chain, adding the specified hook 204 | // as the last ones in the mutation flow. 205 | func (c Chain) Append(hooks ...ent.Hook) Chain { 206 | newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) 207 | newHooks = append(newHooks, c.hooks...) 208 | newHooks = append(newHooks, hooks...) 209 | return Chain{newHooks} 210 | } 211 | 212 | // Extend extends a chain, adding the specified chain 213 | // as the last ones in the mutation flow. 214 | func (c Chain) Extend(chain Chain) Chain { 215 | return c.Append(chain.hooks...) 216 | } 217 | -------------------------------------------------------------------------------- /ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithFixture sets the foreign-key renaming option to the migration when upgrading 32 | // ent from v0.1.0 (issue-#285). Defaults to false. 33 | WithFixture = schema.WithFixture 34 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 35 | WithForeignKeys = schema.WithForeignKeys 36 | ) 37 | 38 | // Schema is the API for creating, migrating and dropping a schema. 39 | type Schema struct { 40 | drv dialect.Driver 41 | universalID bool 42 | } 43 | 44 | // NewSchema creates a new schema client. 45 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 46 | 47 | // Create creates all schema resources. 48 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 49 | migrate, err := schema.NewMigrate(s.drv, opts...) 50 | if err != nil { 51 | return fmt.Errorf("ent/migrate: %w", err) 52 | } 53 | return migrate.Create(ctx, Tables...) 54 | } 55 | 56 | // WriteTo writes the schema changes to w instead of running them against the database. 57 | // 58 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 59 | // log.Fatal(err) 60 | // } 61 | // 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | drv := &schema.WriteDriver{ 64 | Writer: w, 65 | Driver: s.drv, 66 | } 67 | migrate, err := schema.NewMigrate(drv, opts...) 68 | if err != nil { 69 | return fmt.Errorf("ent/migrate: %w", err) 70 | } 71 | return migrate.Create(ctx, Tables...) 72 | } 73 | -------------------------------------------------------------------------------- /ent/migrate/schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql/schema" 7 | "entgo.io/ent/schema/field" 8 | ) 9 | 10 | var ( 11 | // TodosColumns holds the columns for the "todos" table. 12 | TodosColumns = []*schema.Column{ 13 | {Name: "id", Type: field.TypeString}, 14 | {Name: "name", Type: field.TypeString, Default: ""}, 15 | {Name: "status", Type: field.TypeEnum, Enums: []string{"IN_PROGRESS", "COMPLETED"}, Default: "IN_PROGRESS"}, 16 | {Name: "priority", Type: field.TypeInt, Default: 0}, 17 | {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime DEFAULT CURRENT_TIMESTAMP"}}, 18 | {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"}}, 19 | {Name: "user_id", Type: field.TypeString, Nullable: true}, 20 | } 21 | // TodosTable holds the schema information for the "todos" table. 22 | TodosTable = &schema.Table{ 23 | Name: "todos", 24 | Columns: TodosColumns, 25 | PrimaryKey: []*schema.Column{TodosColumns[0]}, 26 | ForeignKeys: []*schema.ForeignKey{ 27 | { 28 | Symbol: "todos_users_todos", 29 | Columns: []*schema.Column{TodosColumns[6]}, 30 | RefColumns: []*schema.Column{UsersColumns[0]}, 31 | OnDelete: schema.SetNull, 32 | }, 33 | }, 34 | } 35 | // UsersColumns holds the columns for the "users" table. 36 | UsersColumns = []*schema.Column{ 37 | {Name: "id", Type: field.TypeString}, 38 | {Name: "name", Type: field.TypeString, Size: 255}, 39 | {Name: "age", Type: field.TypeInt}, 40 | {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime DEFAULT CURRENT_TIMESTAMP"}}, 41 | {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"}}, 42 | } 43 | // UsersTable holds the schema information for the "users" table. 44 | UsersTable = &schema.Table{ 45 | Name: "users", 46 | Columns: UsersColumns, 47 | PrimaryKey: []*schema.Column{UsersColumns[0]}, 48 | } 49 | // Tables holds all the tables in the schema. 50 | Tables = []*schema.Table{ 51 | TodosTable, 52 | UsersTable, 53 | } 54 | ) 55 | 56 | func init() { 57 | TodosTable.ForeignKeys[0].RefTable = UsersTable 58 | } 59 | -------------------------------------------------------------------------------- /ent/mixin/datetime.go: -------------------------------------------------------------------------------- 1 | package mixin 2 | 3 | import ( 4 | "time" 5 | 6 | "entgo.io/ent" 7 | "entgo.io/ent/dialect" 8 | "entgo.io/ent/schema/field" 9 | "entgo.io/ent/schema/mixin" 10 | ) 11 | 12 | // NewDatetime creates a Mixin that includes create_at and updated_at 13 | func NewDatetime() *DatetimeMixin { 14 | return &DatetimeMixin{} 15 | } 16 | 17 | // DatetimeMixin defines an ent Mixin 18 | type DatetimeMixin struct { 19 | mixin.Schema 20 | } 21 | 22 | // Fields provides the created_at and updated_at field. 23 | func (m DatetimeMixin) Fields() []ent.Field { 24 | return []ent.Field{ 25 | field.Time("created_at"). 26 | Default(time.Now). 27 | SchemaType(map[string]string{ 28 | dialect.MySQL: "datetime DEFAULT CURRENT_TIMESTAMP", 29 | }). 30 | Immutable(), 31 | field.Time("updated_at"). 32 | Default(time.Now). 33 | SchemaType(map[string]string{ 34 | dialect.MySQL: "datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", 35 | }). 36 | Immutable(), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ent/mixin/ulid.go: -------------------------------------------------------------------------------- 1 | package mixin 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 5 | 6 | "entgo.io/ent" 7 | "entgo.io/ent/schema" 8 | "entgo.io/ent/schema/field" 9 | "entgo.io/ent/schema/mixin" 10 | ) 11 | 12 | // NewUlid creates a Mixin that encodes the provided prefix. 13 | func NewUlid(prefix string) *UlidMixin { 14 | return &UlidMixin{prefix: prefix} 15 | } 16 | 17 | // UlidMixin defines an ent Mixin that captures the ULID prefix for a type. 18 | type UlidMixin struct { 19 | mixin.Schema 20 | prefix string 21 | } 22 | 23 | // Fields provides the id field. 24 | func (m UlidMixin) Fields() []ent.Field { 25 | return []ent.Field{ 26 | field.String("id"). 27 | GoType(ulid.ID("")). 28 | DefaultFunc(func() ulid.ID { return ulid.MustNew(m.prefix) }), 29 | } 30 | } 31 | 32 | // Annotation captures the id prefix for a type. 33 | type Annotation struct { 34 | Prefix string 35 | } 36 | 37 | // Name implements the ent Annotation interface. 38 | func (a Annotation) Name() string { 39 | return "ULID" 40 | } 41 | 42 | // Annotations returns the annotations for a Mixin instance. 43 | func (m UlidMixin) Annotations() []schema.Annotation { 44 | return []schema.Annotation{ 45 | Annotation{Prefix: m.prefix}, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ent/mutation_input.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 7 | "golang-clean-architecture-ent-gqlgen/ent/todo" 8 | "time" 9 | ) 10 | 11 | // CreateTodoInput represents a mutation input for creating todos. 12 | type CreateTodoInput struct { 13 | Name *string 14 | Status *todo.Status 15 | Priority *int 16 | CreatedAt *time.Time 17 | UpdatedAt *time.Time 18 | UserID *ulid.ID 19 | } 20 | 21 | // Mutate applies the CreateTodoInput on the TodoCreate builder. 22 | func (i *CreateTodoInput) Mutate(m *TodoCreate) { 23 | if v := i.Name; v != nil { 24 | m.SetName(*v) 25 | } 26 | if v := i.Status; v != nil { 27 | m.SetStatus(*v) 28 | } 29 | if v := i.Priority; v != nil { 30 | m.SetPriority(*v) 31 | } 32 | if v := i.CreatedAt; v != nil { 33 | m.SetCreatedAt(*v) 34 | } 35 | if v := i.UpdatedAt; v != nil { 36 | m.SetUpdatedAt(*v) 37 | } 38 | if v := i.UserID; v != nil { 39 | m.SetUserID(*v) 40 | } 41 | } 42 | 43 | // SetInput applies the change-set in the CreateTodoInput on the create builder. 44 | func (c *TodoCreate) SetInput(i CreateTodoInput) *TodoCreate { 45 | i.Mutate(c) 46 | return c 47 | } 48 | 49 | // UpdateTodoInput represents a mutation input for updating todos. 50 | type UpdateTodoInput struct { 51 | ID ulid.ID 52 | Name *string 53 | Status *todo.Status 54 | Priority *int 55 | UserID *ulid.ID 56 | ClearUser bool 57 | } 58 | 59 | // Mutate applies the UpdateTodoInput on the TodoMutation. 60 | func (i *UpdateTodoInput) Mutate(m *TodoMutation) { 61 | if v := i.Name; v != nil { 62 | m.SetName(*v) 63 | } 64 | if v := i.Status; v != nil { 65 | m.SetStatus(*v) 66 | } 67 | if v := i.Priority; v != nil { 68 | m.SetPriority(*v) 69 | } 70 | if i.ClearUser { 71 | m.ClearUser() 72 | } 73 | if v := i.UserID; v != nil { 74 | m.SetUserID(*v) 75 | } 76 | } 77 | 78 | // SetInput applies the change-set in the UpdateTodoInput on the update builder. 79 | func (u *TodoUpdate) SetInput(i UpdateTodoInput) *TodoUpdate { 80 | i.Mutate(u.Mutation()) 81 | return u 82 | } 83 | 84 | // SetInput applies the change-set in the UpdateTodoInput on the update-one builder. 85 | func (u *TodoUpdateOne) SetInput(i UpdateTodoInput) *TodoUpdateOne { 86 | i.Mutate(u.Mutation()) 87 | return u 88 | } 89 | 90 | // CreateUserInput represents a mutation input for creating users. 91 | type CreateUserInput struct { 92 | Name string 93 | Age int 94 | CreatedAt *time.Time 95 | UpdatedAt *time.Time 96 | TodoIDs []ulid.ID 97 | } 98 | 99 | // Mutate applies the CreateUserInput on the UserCreate builder. 100 | func (i *CreateUserInput) Mutate(m *UserCreate) { 101 | m.SetName(i.Name) 102 | m.SetAge(i.Age) 103 | if v := i.CreatedAt; v != nil { 104 | m.SetCreatedAt(*v) 105 | } 106 | if v := i.UpdatedAt; v != nil { 107 | m.SetUpdatedAt(*v) 108 | } 109 | if ids := i.TodoIDs; len(ids) > 0 { 110 | m.AddTodoIDs(ids...) 111 | } 112 | } 113 | 114 | // SetInput applies the change-set in the CreateUserInput on the create builder. 115 | func (c *UserCreate) SetInput(i CreateUserInput) *UserCreate { 116 | i.Mutate(c) 117 | return c 118 | } 119 | 120 | // UpdateUserInput represents a mutation input for updating users. 121 | type UpdateUserInput struct { 122 | ID ulid.ID 123 | Name *string 124 | Age *int 125 | AddTodoIDs []ulid.ID 126 | RemoveTodoIDs []ulid.ID 127 | } 128 | 129 | // Mutate applies the UpdateUserInput on the UserMutation. 130 | func (i *UpdateUserInput) Mutate(m *UserMutation) { 131 | if v := i.Name; v != nil { 132 | m.SetName(*v) 133 | } 134 | if v := i.Age; v != nil { 135 | m.SetAge(*v) 136 | } 137 | if ids := i.AddTodoIDs; len(ids) > 0 { 138 | m.AddTodoIDs(ids...) 139 | } 140 | if ids := i.RemoveTodoIDs; len(ids) > 0 { 141 | m.RemoveTodoIDs(ids...) 142 | } 143 | } 144 | 145 | // SetInput applies the change-set in the UpdateUserInput on the update builder. 146 | func (u *UserUpdate) SetInput(i UpdateUserInput) *UserUpdate { 147 | i.Mutate(u.Mutation()) 148 | return u 149 | } 150 | 151 | // SetInput applies the change-set in the UpdateUserInput on the update-one builder. 152 | func (u *UserUpdateOne) SetInput(i UpdateUserInput) *UserUpdateOne { 153 | i.Mutate(u.Mutation()) 154 | return u 155 | } 156 | -------------------------------------------------------------------------------- /ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // Todo is the predicate function for todo builders. 10 | type Todo func(*sql.Selector) 11 | 12 | // User is the predicate function for user builders. 13 | type User func(*sql.Selector) 14 | -------------------------------------------------------------------------------- /ent/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "golang-clean-architecture-ent-gqlgen/ent/schema" 7 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 8 | "golang-clean-architecture-ent-gqlgen/ent/todo" 9 | "golang-clean-architecture-ent-gqlgen/ent/user" 10 | "time" 11 | ) 12 | 13 | // The init function reads all schema descriptors with runtime code 14 | // (default values, validators, hooks and policies) and stitches it 15 | // to their package variables. 16 | func init() { 17 | todoMixin := schema.Todo{}.Mixin() 18 | todoMixinFields0 := todoMixin[0].Fields() 19 | _ = todoMixinFields0 20 | todoMixinFields1 := todoMixin[1].Fields() 21 | _ = todoMixinFields1 22 | todoMixinFields2 := todoMixin[2].Fields() 23 | _ = todoMixinFields2 24 | todoFields := schema.Todo{}.Fields() 25 | _ = todoFields 26 | // todoDescName is the schema descriptor for name field. 27 | todoDescName := todoMixinFields1[1].Descriptor() 28 | // todo.DefaultName holds the default value on creation for the name field. 29 | todo.DefaultName = todoDescName.Default.(string) 30 | // todoDescPriority is the schema descriptor for priority field. 31 | todoDescPriority := todoMixinFields1[3].Descriptor() 32 | // todo.DefaultPriority holds the default value on creation for the priority field. 33 | todo.DefaultPriority = todoDescPriority.Default.(int) 34 | // todoDescCreatedAt is the schema descriptor for created_at field. 35 | todoDescCreatedAt := todoMixinFields2[0].Descriptor() 36 | // todo.DefaultCreatedAt holds the default value on creation for the created_at field. 37 | todo.DefaultCreatedAt = todoDescCreatedAt.Default.(func() time.Time) 38 | // todoDescUpdatedAt is the schema descriptor for updated_at field. 39 | todoDescUpdatedAt := todoMixinFields2[1].Descriptor() 40 | // todo.DefaultUpdatedAt holds the default value on creation for the updated_at field. 41 | todo.DefaultUpdatedAt = todoDescUpdatedAt.Default.(func() time.Time) 42 | // todoDescID is the schema descriptor for id field. 43 | todoDescID := todoMixinFields0[0].Descriptor() 44 | // todo.DefaultID holds the default value on creation for the id field. 45 | todo.DefaultID = todoDescID.Default.(func() ulid.ID) 46 | userMixin := schema.User{}.Mixin() 47 | userMixinFields0 := userMixin[0].Fields() 48 | _ = userMixinFields0 49 | userMixinFields1 := userMixin[1].Fields() 50 | _ = userMixinFields1 51 | userMixinFields2 := userMixin[2].Fields() 52 | _ = userMixinFields2 53 | userFields := schema.User{}.Fields() 54 | _ = userFields 55 | // userDescName is the schema descriptor for name field. 56 | userDescName := userMixinFields1[0].Descriptor() 57 | // user.NameValidator is a validator for the "name" field. It is called by the builders before save. 58 | user.NameValidator = func() func(string) error { 59 | validators := userDescName.Validators 60 | fns := [...]func(string) error{ 61 | validators[0].(func(string) error), 62 | validators[1].(func(string) error), 63 | } 64 | return func(name string) error { 65 | for _, fn := range fns { 66 | if err := fn(name); err != nil { 67 | return err 68 | } 69 | } 70 | return nil 71 | } 72 | }() 73 | // userDescAge is the schema descriptor for age field. 74 | userDescAge := userMixinFields1[1].Descriptor() 75 | // user.AgeValidator is a validator for the "age" field. It is called by the builders before save. 76 | user.AgeValidator = userDescAge.Validators[0].(func(int) error) 77 | // userDescCreatedAt is the schema descriptor for created_at field. 78 | userDescCreatedAt := userMixinFields2[0].Descriptor() 79 | // user.DefaultCreatedAt holds the default value on creation for the created_at field. 80 | user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) 81 | // userDescUpdatedAt is the schema descriptor for updated_at field. 82 | userDescUpdatedAt := userMixinFields2[1].Descriptor() 83 | // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. 84 | user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) 85 | // userDescID is the schema descriptor for id field. 86 | userDescID := userMixinFields0[0].Descriptor() 87 | // user.DefaultID holds the default value on creation for the id field. 88 | user.DefaultID = userDescID.Default.(func() ulid.ID) 89 | } 90 | -------------------------------------------------------------------------------- /ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in golang-clean-architecture-ent-gqlgen/ent/runtime.go 6 | 7 | const ( 8 | Version = "(devel)" // Version of ent codegen. 9 | ) 10 | -------------------------------------------------------------------------------- /ent/schema/todo.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent/mixin" 5 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 6 | "golang-clean-architecture-ent-gqlgen/pkg/const/globalid" 7 | 8 | "entgo.io/ent/schema/edge" 9 | entMixin "entgo.io/ent/schema/mixin" 10 | 11 | "entgo.io/ent" 12 | "entgo.io/ent/schema/field" 13 | ) 14 | 15 | // Todo holds the schema definition for the Todo entity. 16 | type Todo struct { 17 | ent.Schema 18 | } 19 | 20 | // TodoMixin defines Fields 21 | type TodoMixin struct { 22 | entMixin.Schema 23 | } 24 | 25 | // Fields of the Todo. 26 | func (TodoMixin) Fields() []ent.Field { 27 | return []ent.Field{ 28 | field.String("user_id"). 29 | GoType(ulid.ID("")). 30 | Optional(), 31 | field.String("name").Default(""), 32 | field.Enum("status"). 33 | NamedValues( 34 | "InProgress", "IN_PROGRESS", 35 | "Completed", "COMPLETED", 36 | ). 37 | Default("IN_PROGRESS"), 38 | field.Int("priority").Default(0), 39 | } 40 | } 41 | 42 | // Edges of the Todo. 43 | func (Todo) Edges() []ent.Edge { 44 | return []ent.Edge{ 45 | edge.From("user", User.Type). 46 | Ref("todos"). 47 | Unique(). 48 | Field("user_id"), 49 | } 50 | } 51 | 52 | // Mixin of the Todo. 53 | func (Todo) Mixin() []ent.Mixin { 54 | return []ent.Mixin{ 55 | mixin.NewUlid(globalid.New().Todo.Prefix), 56 | TodoMixin{}, 57 | mixin.NewDatetime(), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ent/schema/ulid/ulid.go: -------------------------------------------------------------------------------- 1 | package ulid 2 | 3 | import ( 4 | "crypto/rand" 5 | "database/sql/driver" 6 | "fmt" 7 | "io" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/oklog/ulid/v2" 12 | ) 13 | 14 | // ID implements a ULID 15 | type ID string 16 | 17 | var defaultEntropySource *ulid.MonotonicEntropy 18 | 19 | func init() { 20 | // Seed the default entropy source. 21 | defaultEntropySource = ulid.Monotonic(rand.Reader, 0) 22 | } 23 | 24 | // newULID returns a new ULID for time.Now() using the default entropy source. 25 | func newULID() ulid.ULID { 26 | return ulid.MustNew(ulid.Timestamp(time.Now()), defaultEntropySource) 27 | } 28 | 29 | // MustNew returns a new ULID for time.Now() given a prefix. This uses the default entropy source. 30 | func MustNew(prefix string) ID { 31 | return ID(prefix + fmt.Sprint(newULID())) 32 | } 33 | 34 | // UnmarshalGQL implements the graphql.Unmarshaller interface. 35 | func (i *ID) UnmarshalGQL(v interface{}) error { 36 | return i.Scan(v) 37 | } 38 | 39 | // MarshalGQL implements the graphql.Marshaler interface. 40 | func (i ID) MarshalGQL(w io.Writer) { 41 | _, _ = io.WriteString(w, strconv.Quote(string(i))) 42 | } 43 | 44 | // Scan implements the Scanner interface. 45 | func (i *ID) Scan(src interface{}) error { 46 | if src == nil { 47 | return fmt.Errorf("ulid: expected a value") 48 | } 49 | 50 | switch s := src.(type) { 51 | case string: 52 | *i = ID(s) 53 | case []byte: 54 | str := string(s) 55 | *i = ID(str) 56 | default: 57 | return fmt.Errorf("ulid: expected a string %v", s) 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // Value implements the driver Valuer interface. 64 | func (i ID) Value() (driver.Value, error) { 65 | return string(i), nil 66 | } 67 | -------------------------------------------------------------------------------- /ent/schema/user.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent/mixin" 5 | "golang-clean-architecture-ent-gqlgen/pkg/const/globalid" 6 | 7 | "entgo.io/ent/schema/edge" 8 | 9 | "entgo.io/ent" 10 | "entgo.io/ent/schema/field" 11 | entMixin "entgo.io/ent/schema/mixin" 12 | ) 13 | 14 | // User holds the schema definition for the Test entity. 15 | type User struct { 16 | ent.Schema 17 | } 18 | 19 | // UserMixin defines Fields 20 | type UserMixin struct { 21 | entMixin.Schema 22 | } 23 | 24 | // Fields of the User. 25 | func (UserMixin) Fields() []ent.Field { 26 | return []ent.Field{ 27 | field.String("name"). 28 | NotEmpty(). 29 | MaxLen(255), 30 | field.Int("age").Positive(), 31 | } 32 | } 33 | 34 | // Edges of the User. 35 | func (User) Edges() []ent.Edge { 36 | return []ent.Edge{ 37 | edge.To("todos", Todo.Type), 38 | } 39 | } 40 | 41 | // Mixin of the User. 42 | func (User) Mixin() []ent.Mixin { 43 | return []ent.Mixin{ 44 | mixin.NewUlid(globalid.New().User.Prefix), 45 | UserMixin{}, 46 | mixin.NewDatetime(), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ent/template/mutation_input.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "mutation_input" }} 2 | 3 | {{- /*gotype: entgo.io/ent/entc/gen.Graph*/ -}} 4 | 5 | {{ $pkg := base $.Config.Package }} 6 | {{- with extend $ "Package" $pkg }} 7 | {{ template "header" . }} 8 | {{- end }} 9 | 10 | {{ template "import" $ }} 11 | 12 | {{- range $n := $.Nodes }} 13 | {{ $input := print "Create" $n.Name "Input" }} 14 | // {{ $input }} represents a mutation input for creating {{ plural $n.Name | lower }}. 15 | type {{ $input }} struct { 16 | {{- range $f := $n.Fields }} 17 | {{- if not $f.IsEdgeField }} 18 | {{ $f.StructField }} {{ if and (or $f.Optional $f.Default) (not $f.Type.RType.IsPtr) }}*{{ end }}{{ $f.Type }} 19 | {{- end }} 20 | {{- end }} 21 | {{- range $e := $n.Edges }} 22 | {{- if $e.Unique }} 23 | {{- $structField := print (pascal $e.Name) "ID" }} 24 | {{ $structField }} {{ if $e.Optional }}*{{ end }}{{ $e.Type.ID.Type }} 25 | {{- else }} 26 | {{- $structField := print (singular $e.Name | pascal) "IDs" }} 27 | {{ $structField }} []{{ $e.Type.ID.Type }} 28 | {{- end }} 29 | {{- end }} 30 | } 31 | 32 | // Mutate applies the {{ $input }} on the {{ $n.CreateName }} builder. 33 | func (i *{{ $input }}) Mutate(m *{{ $n.CreateName }}) { 34 | {{- range $f := $n.Fields }} 35 | {{- if not $f.IsEdgeField }} 36 | {{- if or $f.Optional $f.Default }} 37 | if v := i.{{ $f.StructField }}; v != nil { 38 | m.{{ $f.MutationSet }}(*v) 39 | } 40 | {{- else }} 41 | m.{{ $f.MutationSet }}(i.{{ $f.StructField }}) 42 | {{- end }} 43 | {{- end }} 44 | {{- end }} 45 | {{- range $e := $n.Edges }} 46 | {{- if $e.Unique }} 47 | {{- $structField := print (pascal $e.Name) "ID" }} 48 | {{- if $e.Optional }} 49 | if v := i.{{ $structField }}; v != nil { 50 | m.{{ $e.MutationSet }}(*v) 51 | } 52 | {{- else }} 53 | m.{{ $e.MutationSet }}(i.{{ $structField }}) 54 | {{- end }} 55 | {{- else }} 56 | {{- $structField := print (singular $e.Name | pascal) "IDs" }} 57 | if ids := i.{{ $structField }}; len(ids) > 0 { 58 | m.{{ $e.MutationAdd }}(ids...) 59 | } 60 | {{- end }} 61 | {{- end }} 62 | } 63 | 64 | // SetInput applies the change-set in the {{ $input }} on the create builder. 65 | func(c *{{ $n.CreateName }}) SetInput(i {{ $input }}) *{{ $n.CreateName }} { 66 | i.Mutate(c) 67 | return c 68 | } 69 | 70 | {{ $input = print "Update" $n.Name "Input" }} 71 | // {{ $input }} represents a mutation input for updating {{ plural $n.Name | lower }}. 72 | type {{ $input }} struct { 73 | {{ print "ID" }} {{ $n.ID.Type }} 74 | {{- range $f := $n.MutableFields }} 75 | {{- if not $f.IsEdgeField }} 76 | {{ $f.StructField }} {{ if not $f.Type.RType.IsPtr }}*{{ end }}{{ $f.Type }} 77 | {{- if $f.Optional }} 78 | {{ print "Clear" $f.StructField }} bool 79 | {{- end }} 80 | {{- end }} 81 | {{- end }} 82 | {{- range $e := $n.Edges }} 83 | {{- if $e.Unique }} 84 | {{- $structField := print (pascal $e.Name) "ID" }} 85 | {{ $structField }} *{{ $e.Type.ID.Type }} 86 | {{ $e.MutationClear }} bool 87 | {{- else }} 88 | {{ $e.MutationAdd }} []{{ $e.Type.ID.Type }} 89 | {{ $e.MutationRemove }} []{{ $e.Type.ID.Type }} 90 | {{- end }} 91 | {{- end }} 92 | } 93 | 94 | // Mutate applies the {{ $input }} on the {{ $n.MutationName }}. 95 | func (i *{{ $input }}) Mutate(m *{{ $n.MutationName }}) { 96 | {{- range $f := $n.MutableFields }} 97 | {{- if not $f.IsEdgeField }} 98 | {{- if $f.Optional }} 99 | if i.{{ print "Clear" $f.StructField }} { 100 | m.{{ print "Clear" $f.StructField }}() 101 | } 102 | {{- end }} 103 | if v := i.{{ $f.StructField }}; v != nil { 104 | m.{{ $f.MutationSet }}(*v) 105 | } 106 | {{- end }} 107 | {{- end }} 108 | {{- range $e := $n.Edges }} 109 | {{- if $e.Unique }} 110 | if i.{{ $e.MutationClear }} { 111 | m.{{ $e.MutationClear }}() 112 | } 113 | {{- $structField := print (pascal $e.Name) "ID" }} 114 | if v := i.{{ $structField }}; v != nil { 115 | m.{{ $e.MutationSet }}(*v) 116 | } 117 | {{- else }} 118 | if ids := i.{{ $e.MutationAdd }}; len(ids) > 0 { 119 | m.{{ $e.MutationAdd }}(ids...) 120 | } 121 | if ids := i.{{ $e.MutationRemove }}; len(ids) > 0 { 122 | m.{{ $e.MutationRemove }}(ids...) 123 | } 124 | {{- end }} 125 | {{- end }} 126 | } 127 | 128 | // SetInput applies the change-set in the {{ $input }} on the update builder. 129 | func(u *{{ $n.UpdateName }}) SetInput(i {{ $input }}) *{{ $n.UpdateName }} { 130 | i.Mutate(u.Mutation()) 131 | return u 132 | } 133 | 134 | // SetInput applies the change-set in the {{ $input }} on the update-one builder. 135 | func(u *{{ $n.UpdateOneName }}) SetInput(i {{ $input }}) *{{ $n.UpdateOneName }} { 136 | i.Mutate(u.Mutation()) 137 | return u 138 | } 139 | {{- end }} 140 | {{ end }} 141 | -------------------------------------------------------------------------------- /ent/todo.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 8 | "golang-clean-architecture-ent-gqlgen/ent/todo" 9 | "golang-clean-architecture-ent-gqlgen/ent/user" 10 | "strings" 11 | "time" 12 | 13 | "entgo.io/ent/dialect/sql" 14 | ) 15 | 16 | // Todo is the model entity for the Todo schema. 17 | type Todo struct { 18 | config `json:"-"` 19 | // ID of the ent. 20 | ID ulid.ID `json:"id,omitempty"` 21 | // UserID holds the value of the "user_id" field. 22 | UserID ulid.ID `json:"user_id,omitempty"` 23 | // Name holds the value of the "name" field. 24 | Name string `json:"name,omitempty"` 25 | // Status holds the value of the "status" field. 26 | Status todo.Status `json:"status,omitempty"` 27 | // Priority holds the value of the "priority" field. 28 | Priority int `json:"priority,omitempty"` 29 | // CreatedAt holds the value of the "created_at" field. 30 | CreatedAt time.Time `json:"created_at,omitempty"` 31 | // UpdatedAt holds the value of the "updated_at" field. 32 | UpdatedAt time.Time `json:"updated_at,omitempty"` 33 | // Edges holds the relations/edges for other nodes in the graph. 34 | // The values are being populated by the TodoQuery when eager-loading is set. 35 | Edges TodoEdges `json:"edges"` 36 | } 37 | 38 | // TodoEdges holds the relations/edges for other nodes in the graph. 39 | type TodoEdges struct { 40 | // User holds the value of the user edge. 41 | User *User `json:"user,omitempty"` 42 | // loadedTypes holds the information for reporting if a 43 | // type was loaded (or requested) in eager-loading or not. 44 | loadedTypes [1]bool 45 | } 46 | 47 | // UserOrErr returns the User value or an error if the edge 48 | // was not loaded in eager-loading, or loaded but was not found. 49 | func (e TodoEdges) UserOrErr() (*User, error) { 50 | if e.loadedTypes[0] { 51 | if e.User == nil { 52 | // The edge user was loaded in eager-loading, 53 | // but was not found. 54 | return nil, &NotFoundError{label: user.Label} 55 | } 56 | return e.User, nil 57 | } 58 | return nil, &NotLoadedError{edge: "user"} 59 | } 60 | 61 | // scanValues returns the types for scanning values from sql.Rows. 62 | func (*Todo) scanValues(columns []string) ([]interface{}, error) { 63 | values := make([]interface{}, len(columns)) 64 | for i := range columns { 65 | switch columns[i] { 66 | case todo.FieldPriority: 67 | values[i] = new(sql.NullInt64) 68 | case todo.FieldName, todo.FieldStatus: 69 | values[i] = new(sql.NullString) 70 | case todo.FieldCreatedAt, todo.FieldUpdatedAt: 71 | values[i] = new(sql.NullTime) 72 | case todo.FieldID, todo.FieldUserID: 73 | values[i] = new(ulid.ID) 74 | default: 75 | return nil, fmt.Errorf("unexpected column %q for type Todo", columns[i]) 76 | } 77 | } 78 | return values, nil 79 | } 80 | 81 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 82 | // to the Todo fields. 83 | func (t *Todo) assignValues(columns []string, values []interface{}) error { 84 | if m, n := len(values), len(columns); m < n { 85 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 86 | } 87 | for i := range columns { 88 | switch columns[i] { 89 | case todo.FieldID: 90 | if value, ok := values[i].(*ulid.ID); !ok { 91 | return fmt.Errorf("unexpected type %T for field id", values[i]) 92 | } else if value != nil { 93 | t.ID = *value 94 | } 95 | case todo.FieldUserID: 96 | if value, ok := values[i].(*ulid.ID); !ok { 97 | return fmt.Errorf("unexpected type %T for field user_id", values[i]) 98 | } else if value != nil { 99 | t.UserID = *value 100 | } 101 | case todo.FieldName: 102 | if value, ok := values[i].(*sql.NullString); !ok { 103 | return fmt.Errorf("unexpected type %T for field name", values[i]) 104 | } else if value.Valid { 105 | t.Name = value.String 106 | } 107 | case todo.FieldStatus: 108 | if value, ok := values[i].(*sql.NullString); !ok { 109 | return fmt.Errorf("unexpected type %T for field status", values[i]) 110 | } else if value.Valid { 111 | t.Status = todo.Status(value.String) 112 | } 113 | case todo.FieldPriority: 114 | if value, ok := values[i].(*sql.NullInt64); !ok { 115 | return fmt.Errorf("unexpected type %T for field priority", values[i]) 116 | } else if value.Valid { 117 | t.Priority = int(value.Int64) 118 | } 119 | case todo.FieldCreatedAt: 120 | if value, ok := values[i].(*sql.NullTime); !ok { 121 | return fmt.Errorf("unexpected type %T for field created_at", values[i]) 122 | } else if value.Valid { 123 | t.CreatedAt = value.Time 124 | } 125 | case todo.FieldUpdatedAt: 126 | if value, ok := values[i].(*sql.NullTime); !ok { 127 | return fmt.Errorf("unexpected type %T for field updated_at", values[i]) 128 | } else if value.Valid { 129 | t.UpdatedAt = value.Time 130 | } 131 | } 132 | } 133 | return nil 134 | } 135 | 136 | // QueryUser queries the "user" edge of the Todo entity. 137 | func (t *Todo) QueryUser() *UserQuery { 138 | return (&TodoClient{config: t.config}).QueryUser(t) 139 | } 140 | 141 | // Update returns a builder for updating this Todo. 142 | // Note that you need to call Todo.Unwrap() before calling this method if this Todo 143 | // was returned from a transaction, and the transaction was committed or rolled back. 144 | func (t *Todo) Update() *TodoUpdateOne { 145 | return (&TodoClient{config: t.config}).UpdateOne(t) 146 | } 147 | 148 | // Unwrap unwraps the Todo entity that was returned from a transaction after it was closed, 149 | // so that all future queries will be executed through the driver which created the transaction. 150 | func (t *Todo) Unwrap() *Todo { 151 | tx, ok := t.config.driver.(*txDriver) 152 | if !ok { 153 | panic("ent: Todo is not a transactional entity") 154 | } 155 | t.config.driver = tx.drv 156 | return t 157 | } 158 | 159 | // String implements the fmt.Stringer. 160 | func (t *Todo) String() string { 161 | var builder strings.Builder 162 | builder.WriteString("Todo(") 163 | builder.WriteString(fmt.Sprintf("id=%v", t.ID)) 164 | builder.WriteString(", user_id=") 165 | builder.WriteString(fmt.Sprintf("%v", t.UserID)) 166 | builder.WriteString(", name=") 167 | builder.WriteString(t.Name) 168 | builder.WriteString(", status=") 169 | builder.WriteString(fmt.Sprintf("%v", t.Status)) 170 | builder.WriteString(", priority=") 171 | builder.WriteString(fmt.Sprintf("%v", t.Priority)) 172 | builder.WriteString(", created_at=") 173 | builder.WriteString(t.CreatedAt.Format(time.ANSIC)) 174 | builder.WriteString(", updated_at=") 175 | builder.WriteString(t.UpdatedAt.Format(time.ANSIC)) 176 | builder.WriteByte(')') 177 | return builder.String() 178 | } 179 | 180 | // Todos is a parsable slice of Todo. 181 | type Todos []*Todo 182 | 183 | func (t Todos) config(cfg config) { 184 | for _i := range t { 185 | t[_i].config = cfg 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ent/todo/todo.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package todo 4 | 5 | import ( 6 | "fmt" 7 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 8 | "io" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | const ( 14 | // Label holds the string label denoting the todo type in the database. 15 | Label = "todo" 16 | // FieldID holds the string denoting the id field in the database. 17 | FieldID = "id" 18 | // FieldUserID holds the string denoting the user_id field in the database. 19 | FieldUserID = "user_id" 20 | // FieldName holds the string denoting the name field in the database. 21 | FieldName = "name" 22 | // FieldStatus holds the string denoting the status field in the database. 23 | FieldStatus = "status" 24 | // FieldPriority holds the string denoting the priority field in the database. 25 | FieldPriority = "priority" 26 | // FieldCreatedAt holds the string denoting the created_at field in the database. 27 | FieldCreatedAt = "created_at" 28 | // FieldUpdatedAt holds the string denoting the updated_at field in the database. 29 | FieldUpdatedAt = "updated_at" 30 | // EdgeUser holds the string denoting the user edge name in mutations. 31 | EdgeUser = "user" 32 | // Table holds the table name of the todo in the database. 33 | Table = "todos" 34 | // UserTable is the table that holds the user relation/edge. 35 | UserTable = "todos" 36 | // UserInverseTable is the table name for the User entity. 37 | // It exists in this package in order to avoid circular dependency with the "user" package. 38 | UserInverseTable = "users" 39 | // UserColumn is the table column denoting the user relation/edge. 40 | UserColumn = "user_id" 41 | ) 42 | 43 | // Columns holds all SQL columns for todo fields. 44 | var Columns = []string{ 45 | FieldID, 46 | FieldUserID, 47 | FieldName, 48 | FieldStatus, 49 | FieldPriority, 50 | FieldCreatedAt, 51 | FieldUpdatedAt, 52 | } 53 | 54 | // ValidColumn reports if the column name is valid (part of the table columns). 55 | func ValidColumn(column string) bool { 56 | for i := range Columns { 57 | if column == Columns[i] { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | 64 | var ( 65 | // DefaultName holds the default value on creation for the "name" field. 66 | DefaultName string 67 | // DefaultPriority holds the default value on creation for the "priority" field. 68 | DefaultPriority int 69 | // DefaultCreatedAt holds the default value on creation for the "created_at" field. 70 | DefaultCreatedAt func() time.Time 71 | // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. 72 | DefaultUpdatedAt func() time.Time 73 | // DefaultID holds the default value on creation for the "id" field. 74 | DefaultID func() ulid.ID 75 | ) 76 | 77 | // Status defines the type for the "status" enum field. 78 | type Status string 79 | 80 | // StatusInProgress is the default value of the Status enum. 81 | const DefaultStatus = StatusInProgress 82 | 83 | // Status values. 84 | const ( 85 | StatusInProgress Status = "IN_PROGRESS" 86 | StatusCompleted Status = "COMPLETED" 87 | ) 88 | 89 | func (s Status) String() string { 90 | return string(s) 91 | } 92 | 93 | // StatusValidator is a validator for the "status" field enum values. It is called by the builders before save. 94 | func StatusValidator(s Status) error { 95 | switch s { 96 | case StatusInProgress, StatusCompleted: 97 | return nil 98 | default: 99 | return fmt.Errorf("todo: invalid enum value for status field: %q", s) 100 | } 101 | } 102 | 103 | // MarshalGQL implements graphql.Marshaler interface. 104 | func (s Status) MarshalGQL(w io.Writer) { 105 | io.WriteString(w, strconv.Quote(s.String())) 106 | } 107 | 108 | // UnmarshalGQL implements graphql.Unmarshaler interface. 109 | func (s *Status) UnmarshalGQL(val interface{}) error { 110 | str, ok := val.(string) 111 | if !ok { 112 | return fmt.Errorf("enum %T must be a string", val) 113 | } 114 | *s = Status(str) 115 | if err := StatusValidator(*s); err != nil { 116 | return fmt.Errorf("%s is not a valid Status", str) 117 | } 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /ent/todo_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/ent/todo" 11 | "golang-clean-architecture-ent-gqlgen/ent/user" 12 | "time" 13 | 14 | "entgo.io/ent/dialect/sql/sqlgraph" 15 | "entgo.io/ent/schema/field" 16 | ) 17 | 18 | // TodoCreate is the builder for creating a Todo entity. 19 | type TodoCreate struct { 20 | config 21 | mutation *TodoMutation 22 | hooks []Hook 23 | } 24 | 25 | // SetUserID sets the "user_id" field. 26 | func (tc *TodoCreate) SetUserID(u ulid.ID) *TodoCreate { 27 | tc.mutation.SetUserID(u) 28 | return tc 29 | } 30 | 31 | // SetNillableUserID sets the "user_id" field if the given value is not nil. 32 | func (tc *TodoCreate) SetNillableUserID(u *ulid.ID) *TodoCreate { 33 | if u != nil { 34 | tc.SetUserID(*u) 35 | } 36 | return tc 37 | } 38 | 39 | // SetName sets the "name" field. 40 | func (tc *TodoCreate) SetName(s string) *TodoCreate { 41 | tc.mutation.SetName(s) 42 | return tc 43 | } 44 | 45 | // SetNillableName sets the "name" field if the given value is not nil. 46 | func (tc *TodoCreate) SetNillableName(s *string) *TodoCreate { 47 | if s != nil { 48 | tc.SetName(*s) 49 | } 50 | return tc 51 | } 52 | 53 | // SetStatus sets the "status" field. 54 | func (tc *TodoCreate) SetStatus(t todo.Status) *TodoCreate { 55 | tc.mutation.SetStatus(t) 56 | return tc 57 | } 58 | 59 | // SetNillableStatus sets the "status" field if the given value is not nil. 60 | func (tc *TodoCreate) SetNillableStatus(t *todo.Status) *TodoCreate { 61 | if t != nil { 62 | tc.SetStatus(*t) 63 | } 64 | return tc 65 | } 66 | 67 | // SetPriority sets the "priority" field. 68 | func (tc *TodoCreate) SetPriority(i int) *TodoCreate { 69 | tc.mutation.SetPriority(i) 70 | return tc 71 | } 72 | 73 | // SetNillablePriority sets the "priority" field if the given value is not nil. 74 | func (tc *TodoCreate) SetNillablePriority(i *int) *TodoCreate { 75 | if i != nil { 76 | tc.SetPriority(*i) 77 | } 78 | return tc 79 | } 80 | 81 | // SetCreatedAt sets the "created_at" field. 82 | func (tc *TodoCreate) SetCreatedAt(t time.Time) *TodoCreate { 83 | tc.mutation.SetCreatedAt(t) 84 | return tc 85 | } 86 | 87 | // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. 88 | func (tc *TodoCreate) SetNillableCreatedAt(t *time.Time) *TodoCreate { 89 | if t != nil { 90 | tc.SetCreatedAt(*t) 91 | } 92 | return tc 93 | } 94 | 95 | // SetUpdatedAt sets the "updated_at" field. 96 | func (tc *TodoCreate) SetUpdatedAt(t time.Time) *TodoCreate { 97 | tc.mutation.SetUpdatedAt(t) 98 | return tc 99 | } 100 | 101 | // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. 102 | func (tc *TodoCreate) SetNillableUpdatedAt(t *time.Time) *TodoCreate { 103 | if t != nil { 104 | tc.SetUpdatedAt(*t) 105 | } 106 | return tc 107 | } 108 | 109 | // SetID sets the "id" field. 110 | func (tc *TodoCreate) SetID(u ulid.ID) *TodoCreate { 111 | tc.mutation.SetID(u) 112 | return tc 113 | } 114 | 115 | // SetNillableID sets the "id" field if the given value is not nil. 116 | func (tc *TodoCreate) SetNillableID(u *ulid.ID) *TodoCreate { 117 | if u != nil { 118 | tc.SetID(*u) 119 | } 120 | return tc 121 | } 122 | 123 | // SetUser sets the "user" edge to the User entity. 124 | func (tc *TodoCreate) SetUser(u *User) *TodoCreate { 125 | return tc.SetUserID(u.ID) 126 | } 127 | 128 | // Mutation returns the TodoMutation object of the builder. 129 | func (tc *TodoCreate) Mutation() *TodoMutation { 130 | return tc.mutation 131 | } 132 | 133 | // Save creates the Todo in the database. 134 | func (tc *TodoCreate) Save(ctx context.Context) (*Todo, error) { 135 | var ( 136 | err error 137 | node *Todo 138 | ) 139 | tc.defaults() 140 | if len(tc.hooks) == 0 { 141 | if err = tc.check(); err != nil { 142 | return nil, err 143 | } 144 | node, err = tc.sqlSave(ctx) 145 | } else { 146 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 147 | mutation, ok := m.(*TodoMutation) 148 | if !ok { 149 | return nil, fmt.Errorf("unexpected mutation type %T", m) 150 | } 151 | if err = tc.check(); err != nil { 152 | return nil, err 153 | } 154 | tc.mutation = mutation 155 | if node, err = tc.sqlSave(ctx); err != nil { 156 | return nil, err 157 | } 158 | mutation.id = &node.ID 159 | mutation.done = true 160 | return node, err 161 | }) 162 | for i := len(tc.hooks) - 1; i >= 0; i-- { 163 | if tc.hooks[i] == nil { 164 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 165 | } 166 | mut = tc.hooks[i](mut) 167 | } 168 | if _, err := mut.Mutate(ctx, tc.mutation); err != nil { 169 | return nil, err 170 | } 171 | } 172 | return node, err 173 | } 174 | 175 | // SaveX calls Save and panics if Save returns an error. 176 | func (tc *TodoCreate) SaveX(ctx context.Context) *Todo { 177 | v, err := tc.Save(ctx) 178 | if err != nil { 179 | panic(err) 180 | } 181 | return v 182 | } 183 | 184 | // Exec executes the query. 185 | func (tc *TodoCreate) Exec(ctx context.Context) error { 186 | _, err := tc.Save(ctx) 187 | return err 188 | } 189 | 190 | // ExecX is like Exec, but panics if an error occurs. 191 | func (tc *TodoCreate) ExecX(ctx context.Context) { 192 | if err := tc.Exec(ctx); err != nil { 193 | panic(err) 194 | } 195 | } 196 | 197 | // defaults sets the default values of the builder before save. 198 | func (tc *TodoCreate) defaults() { 199 | if _, ok := tc.mutation.Name(); !ok { 200 | v := todo.DefaultName 201 | tc.mutation.SetName(v) 202 | } 203 | if _, ok := tc.mutation.Status(); !ok { 204 | v := todo.DefaultStatus 205 | tc.mutation.SetStatus(v) 206 | } 207 | if _, ok := tc.mutation.Priority(); !ok { 208 | v := todo.DefaultPriority 209 | tc.mutation.SetPriority(v) 210 | } 211 | if _, ok := tc.mutation.CreatedAt(); !ok { 212 | v := todo.DefaultCreatedAt() 213 | tc.mutation.SetCreatedAt(v) 214 | } 215 | if _, ok := tc.mutation.UpdatedAt(); !ok { 216 | v := todo.DefaultUpdatedAt() 217 | tc.mutation.SetUpdatedAt(v) 218 | } 219 | if _, ok := tc.mutation.ID(); !ok { 220 | v := todo.DefaultID() 221 | tc.mutation.SetID(v) 222 | } 223 | } 224 | 225 | // check runs all checks and user-defined validators on the builder. 226 | func (tc *TodoCreate) check() error { 227 | if _, ok := tc.mutation.Name(); !ok { 228 | return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "name"`)} 229 | } 230 | if _, ok := tc.mutation.Status(); !ok { 231 | return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "status"`)} 232 | } 233 | if v, ok := tc.mutation.Status(); ok { 234 | if err := todo.StatusValidator(v); err != nil { 235 | return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "status": %w`, err)} 236 | } 237 | } 238 | if _, ok := tc.mutation.Priority(); !ok { 239 | return &ValidationError{Name: "priority", err: errors.New(`ent: missing required field "priority"`)} 240 | } 241 | if _, ok := tc.mutation.CreatedAt(); !ok { 242 | return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "created_at"`)} 243 | } 244 | if _, ok := tc.mutation.UpdatedAt(); !ok { 245 | return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "updated_at"`)} 246 | } 247 | return nil 248 | } 249 | 250 | func (tc *TodoCreate) sqlSave(ctx context.Context) (*Todo, error) { 251 | _node, _spec := tc.createSpec() 252 | if err := sqlgraph.CreateNode(ctx, tc.driver, _spec); err != nil { 253 | if sqlgraph.IsConstraintError(err) { 254 | err = &ConstraintError{err.Error(), err} 255 | } 256 | return nil, err 257 | } 258 | if _spec.ID.Value != nil { 259 | _node.ID = _spec.ID.Value.(ulid.ID) 260 | } 261 | return _node, nil 262 | } 263 | 264 | func (tc *TodoCreate) createSpec() (*Todo, *sqlgraph.CreateSpec) { 265 | var ( 266 | _node = &Todo{config: tc.config} 267 | _spec = &sqlgraph.CreateSpec{ 268 | Table: todo.Table, 269 | ID: &sqlgraph.FieldSpec{ 270 | Type: field.TypeString, 271 | Column: todo.FieldID, 272 | }, 273 | } 274 | ) 275 | if id, ok := tc.mutation.ID(); ok { 276 | _node.ID = id 277 | _spec.ID.Value = id 278 | } 279 | if value, ok := tc.mutation.Name(); ok { 280 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 281 | Type: field.TypeString, 282 | Value: value, 283 | Column: todo.FieldName, 284 | }) 285 | _node.Name = value 286 | } 287 | if value, ok := tc.mutation.Status(); ok { 288 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 289 | Type: field.TypeEnum, 290 | Value: value, 291 | Column: todo.FieldStatus, 292 | }) 293 | _node.Status = value 294 | } 295 | if value, ok := tc.mutation.Priority(); ok { 296 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 297 | Type: field.TypeInt, 298 | Value: value, 299 | Column: todo.FieldPriority, 300 | }) 301 | _node.Priority = value 302 | } 303 | if value, ok := tc.mutation.CreatedAt(); ok { 304 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 305 | Type: field.TypeTime, 306 | Value: value, 307 | Column: todo.FieldCreatedAt, 308 | }) 309 | _node.CreatedAt = value 310 | } 311 | if value, ok := tc.mutation.UpdatedAt(); ok { 312 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 313 | Type: field.TypeTime, 314 | Value: value, 315 | Column: todo.FieldUpdatedAt, 316 | }) 317 | _node.UpdatedAt = value 318 | } 319 | if nodes := tc.mutation.UserIDs(); len(nodes) > 0 { 320 | edge := &sqlgraph.EdgeSpec{ 321 | Rel: sqlgraph.M2O, 322 | Inverse: true, 323 | Table: todo.UserTable, 324 | Columns: []string{todo.UserColumn}, 325 | Bidi: false, 326 | Target: &sqlgraph.EdgeTarget{ 327 | IDSpec: &sqlgraph.FieldSpec{ 328 | Type: field.TypeString, 329 | Column: user.FieldID, 330 | }, 331 | }, 332 | } 333 | for _, k := range nodes { 334 | edge.Target.Nodes = append(edge.Target.Nodes, k) 335 | } 336 | _node.UserID = nodes[0] 337 | _spec.Edges = append(_spec.Edges, edge) 338 | } 339 | return _node, _spec 340 | } 341 | 342 | // TodoCreateBulk is the builder for creating many Todo entities in bulk. 343 | type TodoCreateBulk struct { 344 | config 345 | builders []*TodoCreate 346 | } 347 | 348 | // Save creates the Todo entities in the database. 349 | func (tcb *TodoCreateBulk) Save(ctx context.Context) ([]*Todo, error) { 350 | specs := make([]*sqlgraph.CreateSpec, len(tcb.builders)) 351 | nodes := make([]*Todo, len(tcb.builders)) 352 | mutators := make([]Mutator, len(tcb.builders)) 353 | for i := range tcb.builders { 354 | func(i int, root context.Context) { 355 | builder := tcb.builders[i] 356 | builder.defaults() 357 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 358 | mutation, ok := m.(*TodoMutation) 359 | if !ok { 360 | return nil, fmt.Errorf("unexpected mutation type %T", m) 361 | } 362 | if err := builder.check(); err != nil { 363 | return nil, err 364 | } 365 | builder.mutation = mutation 366 | nodes[i], specs[i] = builder.createSpec() 367 | var err error 368 | if i < len(mutators)-1 { 369 | _, err = mutators[i+1].Mutate(root, tcb.builders[i+1].mutation) 370 | } else { 371 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 372 | // Invoke the actual operation on the latest mutation in the chain. 373 | if err = sqlgraph.BatchCreate(ctx, tcb.driver, spec); err != nil { 374 | if sqlgraph.IsConstraintError(err) { 375 | err = &ConstraintError{err.Error(), err} 376 | } 377 | } 378 | } 379 | if err != nil { 380 | return nil, err 381 | } 382 | mutation.id = &nodes[i].ID 383 | mutation.done = true 384 | return nodes[i], nil 385 | }) 386 | for i := len(builder.hooks) - 1; i >= 0; i-- { 387 | mut = builder.hooks[i](mut) 388 | } 389 | mutators[i] = mut 390 | }(i, ctx) 391 | } 392 | if len(mutators) > 0 { 393 | if _, err := mutators[0].Mutate(ctx, tcb.builders[0].mutation); err != nil { 394 | return nil, err 395 | } 396 | } 397 | return nodes, nil 398 | } 399 | 400 | // SaveX is like Save, but panics if an error occurs. 401 | func (tcb *TodoCreateBulk) SaveX(ctx context.Context) []*Todo { 402 | v, err := tcb.Save(ctx) 403 | if err != nil { 404 | panic(err) 405 | } 406 | return v 407 | } 408 | 409 | // Exec executes the query. 410 | func (tcb *TodoCreateBulk) Exec(ctx context.Context) error { 411 | _, err := tcb.Save(ctx) 412 | return err 413 | } 414 | 415 | // ExecX is like Exec, but panics if an error occurs. 416 | func (tcb *TodoCreateBulk) ExecX(ctx context.Context) { 417 | if err := tcb.Exec(ctx); err != nil { 418 | panic(err) 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /ent/todo_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "golang-clean-architecture-ent-gqlgen/ent/predicate" 9 | "golang-clean-architecture-ent-gqlgen/ent/todo" 10 | 11 | "entgo.io/ent/dialect/sql" 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // TodoDelete is the builder for deleting a Todo entity. 17 | type TodoDelete struct { 18 | config 19 | hooks []Hook 20 | mutation *TodoMutation 21 | } 22 | 23 | // Where appends a list predicates to the TodoDelete builder. 24 | func (td *TodoDelete) Where(ps ...predicate.Todo) *TodoDelete { 25 | td.mutation.Where(ps...) 26 | return td 27 | } 28 | 29 | // Exec executes the deletion query and returns how many vertices were deleted. 30 | func (td *TodoDelete) Exec(ctx context.Context) (int, error) { 31 | var ( 32 | err error 33 | affected int 34 | ) 35 | if len(td.hooks) == 0 { 36 | affected, err = td.sqlExec(ctx) 37 | } else { 38 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 39 | mutation, ok := m.(*TodoMutation) 40 | if !ok { 41 | return nil, fmt.Errorf("unexpected mutation type %T", m) 42 | } 43 | td.mutation = mutation 44 | affected, err = td.sqlExec(ctx) 45 | mutation.done = true 46 | return affected, err 47 | }) 48 | for i := len(td.hooks) - 1; i >= 0; i-- { 49 | if td.hooks[i] == nil { 50 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 51 | } 52 | mut = td.hooks[i](mut) 53 | } 54 | if _, err := mut.Mutate(ctx, td.mutation); err != nil { 55 | return 0, err 56 | } 57 | } 58 | return affected, err 59 | } 60 | 61 | // ExecX is like Exec, but panics if an error occurs. 62 | func (td *TodoDelete) ExecX(ctx context.Context) int { 63 | n, err := td.Exec(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return n 68 | } 69 | 70 | func (td *TodoDelete) sqlExec(ctx context.Context) (int, error) { 71 | _spec := &sqlgraph.DeleteSpec{ 72 | Node: &sqlgraph.NodeSpec{ 73 | Table: todo.Table, 74 | ID: &sqlgraph.FieldSpec{ 75 | Type: field.TypeString, 76 | Column: todo.FieldID, 77 | }, 78 | }, 79 | } 80 | if ps := td.mutation.predicates; len(ps) > 0 { 81 | _spec.Predicate = func(selector *sql.Selector) { 82 | for i := range ps { 83 | ps[i](selector) 84 | } 85 | } 86 | } 87 | return sqlgraph.DeleteNodes(ctx, td.driver, _spec) 88 | } 89 | 90 | // TodoDeleteOne is the builder for deleting a single Todo entity. 91 | type TodoDeleteOne struct { 92 | td *TodoDelete 93 | } 94 | 95 | // Exec executes the deletion query. 96 | func (tdo *TodoDeleteOne) Exec(ctx context.Context) error { 97 | n, err := tdo.td.Exec(ctx) 98 | switch { 99 | case err != nil: 100 | return err 101 | case n == 0: 102 | return &NotFoundError{todo.Label} 103 | default: 104 | return nil 105 | } 106 | } 107 | 108 | // ExecX is like Exec, but panics if an error occurs. 109 | func (tdo *TodoDeleteOne) ExecX(ctx context.Context) { 110 | tdo.td.ExecX(ctx) 111 | } 112 | -------------------------------------------------------------------------------- /ent/tx.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "sync" 8 | 9 | "entgo.io/ent/dialect" 10 | ) 11 | 12 | // Tx is a transactional client that is created by calling Client.Tx(). 13 | type Tx struct { 14 | config 15 | // Todo is the client for interacting with the Todo builders. 16 | Todo *TodoClient 17 | // User is the client for interacting with the User builders. 18 | User *UserClient 19 | 20 | // lazily loaded. 21 | client *Client 22 | clientOnce sync.Once 23 | 24 | // completion callbacks. 25 | mu sync.Mutex 26 | onCommit []CommitHook 27 | onRollback []RollbackHook 28 | 29 | // ctx lives for the life of the transaction. It is 30 | // the same context used by the underlying connection. 31 | ctx context.Context 32 | } 33 | 34 | type ( 35 | // Committer is the interface that wraps the Committer method. 36 | Committer interface { 37 | Commit(context.Context, *Tx) error 38 | } 39 | 40 | // The CommitFunc type is an adapter to allow the use of ordinary 41 | // function as a Committer. If f is a function with the appropriate 42 | // signature, CommitFunc(f) is a Committer that calls f. 43 | CommitFunc func(context.Context, *Tx) error 44 | 45 | // CommitHook defines the "commit middleware". A function that gets a Committer 46 | // and returns a Committer. For example: 47 | // 48 | // hook := func(next ent.Committer) ent.Committer { 49 | // return ent.CommitFunc(func(context.Context, tx *ent.Tx) error { 50 | // // Do some stuff before. 51 | // if err := next.Commit(ctx, tx); err != nil { 52 | // return err 53 | // } 54 | // // Do some stuff after. 55 | // return nil 56 | // }) 57 | // } 58 | // 59 | CommitHook func(Committer) Committer 60 | ) 61 | 62 | // Commit calls f(ctx, m). 63 | func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { 64 | return f(ctx, tx) 65 | } 66 | 67 | // Commit commits the transaction. 68 | func (tx *Tx) Commit() error { 69 | txDriver := tx.config.driver.(*txDriver) 70 | var fn Committer = CommitFunc(func(context.Context, *Tx) error { 71 | return txDriver.tx.Commit() 72 | }) 73 | tx.mu.Lock() 74 | hooks := append([]CommitHook(nil), tx.onCommit...) 75 | tx.mu.Unlock() 76 | for i := len(hooks) - 1; i >= 0; i-- { 77 | fn = hooks[i](fn) 78 | } 79 | return fn.Commit(tx.ctx, tx) 80 | } 81 | 82 | // OnCommit adds a hook to call on commit. 83 | func (tx *Tx) OnCommit(f CommitHook) { 84 | tx.mu.Lock() 85 | defer tx.mu.Unlock() 86 | tx.onCommit = append(tx.onCommit, f) 87 | } 88 | 89 | type ( 90 | // Rollbacker is the interface that wraps the Rollbacker method. 91 | Rollbacker interface { 92 | Rollback(context.Context, *Tx) error 93 | } 94 | 95 | // The RollbackFunc type is an adapter to allow the use of ordinary 96 | // function as a Rollbacker. If f is a function with the appropriate 97 | // signature, RollbackFunc(f) is a Rollbacker that calls f. 98 | RollbackFunc func(context.Context, *Tx) error 99 | 100 | // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker 101 | // and returns a Rollbacker. For example: 102 | // 103 | // hook := func(next ent.Rollbacker) ent.Rollbacker { 104 | // return ent.RollbackFunc(func(context.Context, tx *ent.Tx) error { 105 | // // Do some stuff before. 106 | // if err := next.Rollback(ctx, tx); err != nil { 107 | // return err 108 | // } 109 | // // Do some stuff after. 110 | // return nil 111 | // }) 112 | // } 113 | // 114 | RollbackHook func(Rollbacker) Rollbacker 115 | ) 116 | 117 | // Rollback calls f(ctx, m). 118 | func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { 119 | return f(ctx, tx) 120 | } 121 | 122 | // Rollback rollbacks the transaction. 123 | func (tx *Tx) Rollback() error { 124 | txDriver := tx.config.driver.(*txDriver) 125 | var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { 126 | return txDriver.tx.Rollback() 127 | }) 128 | tx.mu.Lock() 129 | hooks := append([]RollbackHook(nil), tx.onRollback...) 130 | tx.mu.Unlock() 131 | for i := len(hooks) - 1; i >= 0; i-- { 132 | fn = hooks[i](fn) 133 | } 134 | return fn.Rollback(tx.ctx, tx) 135 | } 136 | 137 | // OnRollback adds a hook to call on rollback. 138 | func (tx *Tx) OnRollback(f RollbackHook) { 139 | tx.mu.Lock() 140 | defer tx.mu.Unlock() 141 | tx.onRollback = append(tx.onRollback, f) 142 | } 143 | 144 | // Client returns a Client that binds to current transaction. 145 | func (tx *Tx) Client() *Client { 146 | tx.clientOnce.Do(func() { 147 | tx.client = &Client{config: tx.config} 148 | tx.client.init() 149 | }) 150 | return tx.client 151 | } 152 | 153 | func (tx *Tx) init() { 154 | tx.Todo = NewTodoClient(tx.config) 155 | tx.User = NewUserClient(tx.config) 156 | } 157 | 158 | // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. 159 | // The idea is to support transactions without adding any extra code to the builders. 160 | // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. 161 | // Commit and Rollback are nop for the internal builders and the user must call one 162 | // of them in order to commit or rollback the transaction. 163 | // 164 | // If a closed transaction is embedded in one of the generated entities, and the entity 165 | // applies a query, for example: Todo.QueryXXX(), the query will be executed 166 | // through the driver which created this transaction. 167 | // 168 | // Note that txDriver is not goroutine safe. 169 | type txDriver struct { 170 | // the driver we started the transaction from. 171 | drv dialect.Driver 172 | // tx is the underlying transaction. 173 | tx dialect.Tx 174 | } 175 | 176 | // newTx creates a new transactional driver. 177 | func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { 178 | tx, err := drv.Tx(ctx) 179 | if err != nil { 180 | return nil, err 181 | } 182 | return &txDriver{tx: tx, drv: drv}, nil 183 | } 184 | 185 | // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls 186 | // from the internal builders. Should be called only by the internal builders. 187 | func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } 188 | 189 | // Dialect returns the dialect of the driver we started the transaction from. 190 | func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } 191 | 192 | // Close is a nop close. 193 | func (*txDriver) Close() error { return nil } 194 | 195 | // Commit is a nop commit for the internal builders. 196 | // User must call `Tx.Commit` in order to commit the transaction. 197 | func (*txDriver) Commit() error { return nil } 198 | 199 | // Rollback is a nop rollback for the internal builders. 200 | // User must call `Tx.Rollback` in order to rollback the transaction. 201 | func (*txDriver) Rollback() error { return nil } 202 | 203 | // Exec calls tx.Exec. 204 | func (tx *txDriver) Exec(ctx context.Context, query string, args, v interface{}) error { 205 | return tx.tx.Exec(ctx, query, args, v) 206 | } 207 | 208 | // Query calls tx.Query. 209 | func (tx *txDriver) Query(ctx context.Context, query string, args, v interface{}) error { 210 | return tx.tx.Query(ctx, query, args, v) 211 | } 212 | 213 | var _ dialect.Driver = (*txDriver)(nil) 214 | -------------------------------------------------------------------------------- /ent/ulid.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 7 | "golang-clean-architecture-ent-gqlgen/pkg/const/globalid" 8 | ) 9 | 10 | var globalIDs = globalid.New() 11 | 12 | // IDToType maps an ulid.ID to the underlying table. 13 | func IDToType(_ context.Context, id ulid.ID) (string, error) { 14 | if len(id) < 3 { 15 | return "", fmt.Errorf("IDToType: id too short") 16 | } 17 | prefix := id[:3] 18 | t, err := globalIDs.FindTableByID(string(prefix)) 19 | if err != nil { 20 | return "", fmt.Errorf("IDToType: could not map prefix '%s' to a type", prefix) 21 | } 22 | return t, nil 23 | } 24 | -------------------------------------------------------------------------------- /ent/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 8 | "golang-clean-architecture-ent-gqlgen/ent/user" 9 | "strings" 10 | "time" 11 | 12 | "entgo.io/ent/dialect/sql" 13 | ) 14 | 15 | // User is the model entity for the User schema. 16 | type User struct { 17 | config `json:"-"` 18 | // ID of the ent. 19 | ID ulid.ID `json:"id,omitempty"` 20 | // Name holds the value of the "name" field. 21 | Name string `json:"name,omitempty"` 22 | // Age holds the value of the "age" field. 23 | Age int `json:"age,omitempty"` 24 | // CreatedAt holds the value of the "created_at" field. 25 | CreatedAt time.Time `json:"created_at,omitempty"` 26 | // UpdatedAt holds the value of the "updated_at" field. 27 | UpdatedAt time.Time `json:"updated_at,omitempty"` 28 | // Edges holds the relations/edges for other nodes in the graph. 29 | // The values are being populated by the UserQuery when eager-loading is set. 30 | Edges UserEdges `json:"edges"` 31 | } 32 | 33 | // UserEdges holds the relations/edges for other nodes in the graph. 34 | type UserEdges struct { 35 | // Todos holds the value of the todos edge. 36 | Todos []*Todo `json:"todos,omitempty"` 37 | // loadedTypes holds the information for reporting if a 38 | // type was loaded (or requested) in eager-loading or not. 39 | loadedTypes [1]bool 40 | } 41 | 42 | // TodosOrErr returns the Todos value or an error if the edge 43 | // was not loaded in eager-loading. 44 | func (e UserEdges) TodosOrErr() ([]*Todo, error) { 45 | if e.loadedTypes[0] { 46 | return e.Todos, nil 47 | } 48 | return nil, &NotLoadedError{edge: "todos"} 49 | } 50 | 51 | // scanValues returns the types for scanning values from sql.Rows. 52 | func (*User) scanValues(columns []string) ([]interface{}, error) { 53 | values := make([]interface{}, len(columns)) 54 | for i := range columns { 55 | switch columns[i] { 56 | case user.FieldAge: 57 | values[i] = new(sql.NullInt64) 58 | case user.FieldName: 59 | values[i] = new(sql.NullString) 60 | case user.FieldCreatedAt, user.FieldUpdatedAt: 61 | values[i] = new(sql.NullTime) 62 | case user.FieldID: 63 | values[i] = new(ulid.ID) 64 | default: 65 | return nil, fmt.Errorf("unexpected column %q for type User", columns[i]) 66 | } 67 | } 68 | return values, nil 69 | } 70 | 71 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 72 | // to the User fields. 73 | func (u *User) assignValues(columns []string, values []interface{}) error { 74 | if m, n := len(values), len(columns); m < n { 75 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 76 | } 77 | for i := range columns { 78 | switch columns[i] { 79 | case user.FieldID: 80 | if value, ok := values[i].(*ulid.ID); !ok { 81 | return fmt.Errorf("unexpected type %T for field id", values[i]) 82 | } else if value != nil { 83 | u.ID = *value 84 | } 85 | case user.FieldName: 86 | if value, ok := values[i].(*sql.NullString); !ok { 87 | return fmt.Errorf("unexpected type %T for field name", values[i]) 88 | } else if value.Valid { 89 | u.Name = value.String 90 | } 91 | case user.FieldAge: 92 | if value, ok := values[i].(*sql.NullInt64); !ok { 93 | return fmt.Errorf("unexpected type %T for field age", values[i]) 94 | } else if value.Valid { 95 | u.Age = int(value.Int64) 96 | } 97 | case user.FieldCreatedAt: 98 | if value, ok := values[i].(*sql.NullTime); !ok { 99 | return fmt.Errorf("unexpected type %T for field created_at", values[i]) 100 | } else if value.Valid { 101 | u.CreatedAt = value.Time 102 | } 103 | case user.FieldUpdatedAt: 104 | if value, ok := values[i].(*sql.NullTime); !ok { 105 | return fmt.Errorf("unexpected type %T for field updated_at", values[i]) 106 | } else if value.Valid { 107 | u.UpdatedAt = value.Time 108 | } 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | // QueryTodos queries the "todos" edge of the User entity. 115 | func (u *User) QueryTodos() *TodoQuery { 116 | return (&UserClient{config: u.config}).QueryTodos(u) 117 | } 118 | 119 | // Update returns a builder for updating this User. 120 | // Note that you need to call User.Unwrap() before calling this method if this User 121 | // was returned from a transaction, and the transaction was committed or rolled back. 122 | func (u *User) Update() *UserUpdateOne { 123 | return (&UserClient{config: u.config}).UpdateOne(u) 124 | } 125 | 126 | // Unwrap unwraps the User entity that was returned from a transaction after it was closed, 127 | // so that all future queries will be executed through the driver which created the transaction. 128 | func (u *User) Unwrap() *User { 129 | tx, ok := u.config.driver.(*txDriver) 130 | if !ok { 131 | panic("ent: User is not a transactional entity") 132 | } 133 | u.config.driver = tx.drv 134 | return u 135 | } 136 | 137 | // String implements the fmt.Stringer. 138 | func (u *User) String() string { 139 | var builder strings.Builder 140 | builder.WriteString("User(") 141 | builder.WriteString(fmt.Sprintf("id=%v", u.ID)) 142 | builder.WriteString(", name=") 143 | builder.WriteString(u.Name) 144 | builder.WriteString(", age=") 145 | builder.WriteString(fmt.Sprintf("%v", u.Age)) 146 | builder.WriteString(", created_at=") 147 | builder.WriteString(u.CreatedAt.Format(time.ANSIC)) 148 | builder.WriteString(", updated_at=") 149 | builder.WriteString(u.UpdatedAt.Format(time.ANSIC)) 150 | builder.WriteByte(')') 151 | return builder.String() 152 | } 153 | 154 | // Users is a parsable slice of User. 155 | type Users []*User 156 | 157 | func (u Users) config(cfg config) { 158 | for _i := range u { 159 | u[_i].config = cfg 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /ent/user/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package user 4 | 5 | import ( 6 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 7 | "time" 8 | ) 9 | 10 | const ( 11 | // Label holds the string label denoting the user type in the database. 12 | Label = "user" 13 | // FieldID holds the string denoting the id field in the database. 14 | FieldID = "id" 15 | // FieldName holds the string denoting the name field in the database. 16 | FieldName = "name" 17 | // FieldAge holds the string denoting the age field in the database. 18 | FieldAge = "age" 19 | // FieldCreatedAt holds the string denoting the created_at field in the database. 20 | FieldCreatedAt = "created_at" 21 | // FieldUpdatedAt holds the string denoting the updated_at field in the database. 22 | FieldUpdatedAt = "updated_at" 23 | // EdgeTodos holds the string denoting the todos edge name in mutations. 24 | EdgeTodos = "todos" 25 | // Table holds the table name of the user in the database. 26 | Table = "users" 27 | // TodosTable is the table that holds the todos relation/edge. 28 | TodosTable = "todos" 29 | // TodosInverseTable is the table name for the Todo entity. 30 | // It exists in this package in order to avoid circular dependency with the "todo" package. 31 | TodosInverseTable = "todos" 32 | // TodosColumn is the table column denoting the todos relation/edge. 33 | TodosColumn = "user_id" 34 | ) 35 | 36 | // Columns holds all SQL columns for user fields. 37 | var Columns = []string{ 38 | FieldID, 39 | FieldName, 40 | FieldAge, 41 | FieldCreatedAt, 42 | FieldUpdatedAt, 43 | } 44 | 45 | // ValidColumn reports if the column name is valid (part of the table columns). 46 | func ValidColumn(column string) bool { 47 | for i := range Columns { 48 | if column == Columns[i] { 49 | return true 50 | } 51 | } 52 | return false 53 | } 54 | 55 | var ( 56 | // NameValidator is a validator for the "name" field. It is called by the builders before save. 57 | NameValidator func(string) error 58 | // AgeValidator is a validator for the "age" field. It is called by the builders before save. 59 | AgeValidator func(int) error 60 | // DefaultCreatedAt holds the default value on creation for the "created_at" field. 61 | DefaultCreatedAt func() time.Time 62 | // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. 63 | DefaultUpdatedAt func() time.Time 64 | // DefaultID holds the default value on creation for the "id" field. 65 | DefaultID func() ulid.ID 66 | ) 67 | -------------------------------------------------------------------------------- /ent/user_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/ent/todo" 11 | "golang-clean-architecture-ent-gqlgen/ent/user" 12 | "time" 13 | 14 | "entgo.io/ent/dialect/sql/sqlgraph" 15 | "entgo.io/ent/schema/field" 16 | ) 17 | 18 | // UserCreate is the builder for creating a User entity. 19 | type UserCreate struct { 20 | config 21 | mutation *UserMutation 22 | hooks []Hook 23 | } 24 | 25 | // SetName sets the "name" field. 26 | func (uc *UserCreate) SetName(s string) *UserCreate { 27 | uc.mutation.SetName(s) 28 | return uc 29 | } 30 | 31 | // SetAge sets the "age" field. 32 | func (uc *UserCreate) SetAge(i int) *UserCreate { 33 | uc.mutation.SetAge(i) 34 | return uc 35 | } 36 | 37 | // SetCreatedAt sets the "created_at" field. 38 | func (uc *UserCreate) SetCreatedAt(t time.Time) *UserCreate { 39 | uc.mutation.SetCreatedAt(t) 40 | return uc 41 | } 42 | 43 | // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. 44 | func (uc *UserCreate) SetNillableCreatedAt(t *time.Time) *UserCreate { 45 | if t != nil { 46 | uc.SetCreatedAt(*t) 47 | } 48 | return uc 49 | } 50 | 51 | // SetUpdatedAt sets the "updated_at" field. 52 | func (uc *UserCreate) SetUpdatedAt(t time.Time) *UserCreate { 53 | uc.mutation.SetUpdatedAt(t) 54 | return uc 55 | } 56 | 57 | // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. 58 | func (uc *UserCreate) SetNillableUpdatedAt(t *time.Time) *UserCreate { 59 | if t != nil { 60 | uc.SetUpdatedAt(*t) 61 | } 62 | return uc 63 | } 64 | 65 | // SetID sets the "id" field. 66 | func (uc *UserCreate) SetID(u ulid.ID) *UserCreate { 67 | uc.mutation.SetID(u) 68 | return uc 69 | } 70 | 71 | // SetNillableID sets the "id" field if the given value is not nil. 72 | func (uc *UserCreate) SetNillableID(u *ulid.ID) *UserCreate { 73 | if u != nil { 74 | uc.SetID(*u) 75 | } 76 | return uc 77 | } 78 | 79 | // AddTodoIDs adds the "todos" edge to the Todo entity by IDs. 80 | func (uc *UserCreate) AddTodoIDs(ids ...ulid.ID) *UserCreate { 81 | uc.mutation.AddTodoIDs(ids...) 82 | return uc 83 | } 84 | 85 | // AddTodos adds the "todos" edges to the Todo entity. 86 | func (uc *UserCreate) AddTodos(t ...*Todo) *UserCreate { 87 | ids := make([]ulid.ID, len(t)) 88 | for i := range t { 89 | ids[i] = t[i].ID 90 | } 91 | return uc.AddTodoIDs(ids...) 92 | } 93 | 94 | // Mutation returns the UserMutation object of the builder. 95 | func (uc *UserCreate) Mutation() *UserMutation { 96 | return uc.mutation 97 | } 98 | 99 | // Save creates the User in the database. 100 | func (uc *UserCreate) Save(ctx context.Context) (*User, error) { 101 | var ( 102 | err error 103 | node *User 104 | ) 105 | uc.defaults() 106 | if len(uc.hooks) == 0 { 107 | if err = uc.check(); err != nil { 108 | return nil, err 109 | } 110 | node, err = uc.sqlSave(ctx) 111 | } else { 112 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 113 | mutation, ok := m.(*UserMutation) 114 | if !ok { 115 | return nil, fmt.Errorf("unexpected mutation type %T", m) 116 | } 117 | if err = uc.check(); err != nil { 118 | return nil, err 119 | } 120 | uc.mutation = mutation 121 | if node, err = uc.sqlSave(ctx); err != nil { 122 | return nil, err 123 | } 124 | mutation.id = &node.ID 125 | mutation.done = true 126 | return node, err 127 | }) 128 | for i := len(uc.hooks) - 1; i >= 0; i-- { 129 | if uc.hooks[i] == nil { 130 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 131 | } 132 | mut = uc.hooks[i](mut) 133 | } 134 | if _, err := mut.Mutate(ctx, uc.mutation); err != nil { 135 | return nil, err 136 | } 137 | } 138 | return node, err 139 | } 140 | 141 | // SaveX calls Save and panics if Save returns an error. 142 | func (uc *UserCreate) SaveX(ctx context.Context) *User { 143 | v, err := uc.Save(ctx) 144 | if err != nil { 145 | panic(err) 146 | } 147 | return v 148 | } 149 | 150 | // Exec executes the query. 151 | func (uc *UserCreate) Exec(ctx context.Context) error { 152 | _, err := uc.Save(ctx) 153 | return err 154 | } 155 | 156 | // ExecX is like Exec, but panics if an error occurs. 157 | func (uc *UserCreate) ExecX(ctx context.Context) { 158 | if err := uc.Exec(ctx); err != nil { 159 | panic(err) 160 | } 161 | } 162 | 163 | // defaults sets the default values of the builder before save. 164 | func (uc *UserCreate) defaults() { 165 | if _, ok := uc.mutation.CreatedAt(); !ok { 166 | v := user.DefaultCreatedAt() 167 | uc.mutation.SetCreatedAt(v) 168 | } 169 | if _, ok := uc.mutation.UpdatedAt(); !ok { 170 | v := user.DefaultUpdatedAt() 171 | uc.mutation.SetUpdatedAt(v) 172 | } 173 | if _, ok := uc.mutation.ID(); !ok { 174 | v := user.DefaultID() 175 | uc.mutation.SetID(v) 176 | } 177 | } 178 | 179 | // check runs all checks and user-defined validators on the builder. 180 | func (uc *UserCreate) check() error { 181 | if _, ok := uc.mutation.Name(); !ok { 182 | return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "name"`)} 183 | } 184 | if v, ok := uc.mutation.Name(); ok { 185 | if err := user.NameValidator(v); err != nil { 186 | return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "name": %w`, err)} 187 | } 188 | } 189 | if _, ok := uc.mutation.Age(); !ok { 190 | return &ValidationError{Name: "age", err: errors.New(`ent: missing required field "age"`)} 191 | } 192 | if v, ok := uc.mutation.Age(); ok { 193 | if err := user.AgeValidator(v); err != nil { 194 | return &ValidationError{Name: "age", err: fmt.Errorf(`ent: validator failed for field "age": %w`, err)} 195 | } 196 | } 197 | if _, ok := uc.mutation.CreatedAt(); !ok { 198 | return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "created_at"`)} 199 | } 200 | if _, ok := uc.mutation.UpdatedAt(); !ok { 201 | return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "updated_at"`)} 202 | } 203 | return nil 204 | } 205 | 206 | func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { 207 | _node, _spec := uc.createSpec() 208 | if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil { 209 | if sqlgraph.IsConstraintError(err) { 210 | err = &ConstraintError{err.Error(), err} 211 | } 212 | return nil, err 213 | } 214 | if _spec.ID.Value != nil { 215 | _node.ID = _spec.ID.Value.(ulid.ID) 216 | } 217 | return _node, nil 218 | } 219 | 220 | func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { 221 | var ( 222 | _node = &User{config: uc.config} 223 | _spec = &sqlgraph.CreateSpec{ 224 | Table: user.Table, 225 | ID: &sqlgraph.FieldSpec{ 226 | Type: field.TypeString, 227 | Column: user.FieldID, 228 | }, 229 | } 230 | ) 231 | if id, ok := uc.mutation.ID(); ok { 232 | _node.ID = id 233 | _spec.ID.Value = id 234 | } 235 | if value, ok := uc.mutation.Name(); ok { 236 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 237 | Type: field.TypeString, 238 | Value: value, 239 | Column: user.FieldName, 240 | }) 241 | _node.Name = value 242 | } 243 | if value, ok := uc.mutation.Age(); ok { 244 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 245 | Type: field.TypeInt, 246 | Value: value, 247 | Column: user.FieldAge, 248 | }) 249 | _node.Age = value 250 | } 251 | if value, ok := uc.mutation.CreatedAt(); ok { 252 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 253 | Type: field.TypeTime, 254 | Value: value, 255 | Column: user.FieldCreatedAt, 256 | }) 257 | _node.CreatedAt = value 258 | } 259 | if value, ok := uc.mutation.UpdatedAt(); ok { 260 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 261 | Type: field.TypeTime, 262 | Value: value, 263 | Column: user.FieldUpdatedAt, 264 | }) 265 | _node.UpdatedAt = value 266 | } 267 | if nodes := uc.mutation.TodosIDs(); len(nodes) > 0 { 268 | edge := &sqlgraph.EdgeSpec{ 269 | Rel: sqlgraph.O2M, 270 | Inverse: false, 271 | Table: user.TodosTable, 272 | Columns: []string{user.TodosColumn}, 273 | Bidi: false, 274 | Target: &sqlgraph.EdgeTarget{ 275 | IDSpec: &sqlgraph.FieldSpec{ 276 | Type: field.TypeString, 277 | Column: todo.FieldID, 278 | }, 279 | }, 280 | } 281 | for _, k := range nodes { 282 | edge.Target.Nodes = append(edge.Target.Nodes, k) 283 | } 284 | _spec.Edges = append(_spec.Edges, edge) 285 | } 286 | return _node, _spec 287 | } 288 | 289 | // UserCreateBulk is the builder for creating many User entities in bulk. 290 | type UserCreateBulk struct { 291 | config 292 | builders []*UserCreate 293 | } 294 | 295 | // Save creates the User entities in the database. 296 | func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { 297 | specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) 298 | nodes := make([]*User, len(ucb.builders)) 299 | mutators := make([]Mutator, len(ucb.builders)) 300 | for i := range ucb.builders { 301 | func(i int, root context.Context) { 302 | builder := ucb.builders[i] 303 | builder.defaults() 304 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 305 | mutation, ok := m.(*UserMutation) 306 | if !ok { 307 | return nil, fmt.Errorf("unexpected mutation type %T", m) 308 | } 309 | if err := builder.check(); err != nil { 310 | return nil, err 311 | } 312 | builder.mutation = mutation 313 | nodes[i], specs[i] = builder.createSpec() 314 | var err error 315 | if i < len(mutators)-1 { 316 | _, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation) 317 | } else { 318 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 319 | // Invoke the actual operation on the latest mutation in the chain. 320 | if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil { 321 | if sqlgraph.IsConstraintError(err) { 322 | err = &ConstraintError{err.Error(), err} 323 | } 324 | } 325 | } 326 | if err != nil { 327 | return nil, err 328 | } 329 | mutation.id = &nodes[i].ID 330 | mutation.done = true 331 | return nodes[i], nil 332 | }) 333 | for i := len(builder.hooks) - 1; i >= 0; i-- { 334 | mut = builder.hooks[i](mut) 335 | } 336 | mutators[i] = mut 337 | }(i, ctx) 338 | } 339 | if len(mutators) > 0 { 340 | if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil { 341 | return nil, err 342 | } 343 | } 344 | return nodes, nil 345 | } 346 | 347 | // SaveX is like Save, but panics if an error occurs. 348 | func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User { 349 | v, err := ucb.Save(ctx) 350 | if err != nil { 351 | panic(err) 352 | } 353 | return v 354 | } 355 | 356 | // Exec executes the query. 357 | func (ucb *UserCreateBulk) Exec(ctx context.Context) error { 358 | _, err := ucb.Save(ctx) 359 | return err 360 | } 361 | 362 | // ExecX is like Exec, but panics if an error occurs. 363 | func (ucb *UserCreateBulk) ExecX(ctx context.Context) { 364 | if err := ucb.Exec(ctx); err != nil { 365 | panic(err) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /ent/user_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "golang-clean-architecture-ent-gqlgen/ent/predicate" 9 | "golang-clean-architecture-ent-gqlgen/ent/user" 10 | 11 | "entgo.io/ent/dialect/sql" 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // UserDelete is the builder for deleting a User entity. 17 | type UserDelete struct { 18 | config 19 | hooks []Hook 20 | mutation *UserMutation 21 | } 22 | 23 | // Where appends a list predicates to the UserDelete builder. 24 | func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete { 25 | ud.mutation.Where(ps...) 26 | return ud 27 | } 28 | 29 | // Exec executes the deletion query and returns how many vertices were deleted. 30 | func (ud *UserDelete) Exec(ctx context.Context) (int, error) { 31 | var ( 32 | err error 33 | affected int 34 | ) 35 | if len(ud.hooks) == 0 { 36 | affected, err = ud.sqlExec(ctx) 37 | } else { 38 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 39 | mutation, ok := m.(*UserMutation) 40 | if !ok { 41 | return nil, fmt.Errorf("unexpected mutation type %T", m) 42 | } 43 | ud.mutation = mutation 44 | affected, err = ud.sqlExec(ctx) 45 | mutation.done = true 46 | return affected, err 47 | }) 48 | for i := len(ud.hooks) - 1; i >= 0; i-- { 49 | if ud.hooks[i] == nil { 50 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 51 | } 52 | mut = ud.hooks[i](mut) 53 | } 54 | if _, err := mut.Mutate(ctx, ud.mutation); err != nil { 55 | return 0, err 56 | } 57 | } 58 | return affected, err 59 | } 60 | 61 | // ExecX is like Exec, but panics if an error occurs. 62 | func (ud *UserDelete) ExecX(ctx context.Context) int { 63 | n, err := ud.Exec(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return n 68 | } 69 | 70 | func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) { 71 | _spec := &sqlgraph.DeleteSpec{ 72 | Node: &sqlgraph.NodeSpec{ 73 | Table: user.Table, 74 | ID: &sqlgraph.FieldSpec{ 75 | Type: field.TypeString, 76 | Column: user.FieldID, 77 | }, 78 | }, 79 | } 80 | if ps := ud.mutation.predicates; len(ps) > 0 { 81 | _spec.Predicate = func(selector *sql.Selector) { 82 | for i := range ps { 83 | ps[i](selector) 84 | } 85 | } 86 | } 87 | return sqlgraph.DeleteNodes(ctx, ud.driver, _spec) 88 | } 89 | 90 | // UserDeleteOne is the builder for deleting a single User entity. 91 | type UserDeleteOne struct { 92 | ud *UserDelete 93 | } 94 | 95 | // Exec executes the deletion query. 96 | func (udo *UserDeleteOne) Exec(ctx context.Context) error { 97 | n, err := udo.ud.Exec(ctx) 98 | switch { 99 | case err != nil: 100 | return err 101 | case n == 0: 102 | return &NotFoundError{user.Label} 103 | default: 104 | return nil 105 | } 106 | } 107 | 108 | // ExecX is like Exec, but panics if an error occurs. 109 | func (udo *UserDeleteOne) ExecX(ctx context.Context) { 110 | udo.ud.ExecX(ctx) 111 | } 112 | -------------------------------------------------------------------------------- /ent/user_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "golang-clean-architecture-ent-gqlgen/ent/predicate" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/ent/todo" 11 | "golang-clean-architecture-ent-gqlgen/ent/user" 12 | 13 | "entgo.io/ent/dialect/sql" 14 | "entgo.io/ent/dialect/sql/sqlgraph" 15 | "entgo.io/ent/schema/field" 16 | ) 17 | 18 | // UserUpdate is the builder for updating User entities. 19 | type UserUpdate struct { 20 | config 21 | hooks []Hook 22 | mutation *UserMutation 23 | } 24 | 25 | // Where appends a list predicates to the UserUpdate builder. 26 | func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { 27 | uu.mutation.Where(ps...) 28 | return uu 29 | } 30 | 31 | // SetName sets the "name" field. 32 | func (uu *UserUpdate) SetName(s string) *UserUpdate { 33 | uu.mutation.SetName(s) 34 | return uu 35 | } 36 | 37 | // SetAge sets the "age" field. 38 | func (uu *UserUpdate) SetAge(i int) *UserUpdate { 39 | uu.mutation.ResetAge() 40 | uu.mutation.SetAge(i) 41 | return uu 42 | } 43 | 44 | // AddAge adds i to the "age" field. 45 | func (uu *UserUpdate) AddAge(i int) *UserUpdate { 46 | uu.mutation.AddAge(i) 47 | return uu 48 | } 49 | 50 | // AddTodoIDs adds the "todos" edge to the Todo entity by IDs. 51 | func (uu *UserUpdate) AddTodoIDs(ids ...ulid.ID) *UserUpdate { 52 | uu.mutation.AddTodoIDs(ids...) 53 | return uu 54 | } 55 | 56 | // AddTodos adds the "todos" edges to the Todo entity. 57 | func (uu *UserUpdate) AddTodos(t ...*Todo) *UserUpdate { 58 | ids := make([]ulid.ID, len(t)) 59 | for i := range t { 60 | ids[i] = t[i].ID 61 | } 62 | return uu.AddTodoIDs(ids...) 63 | } 64 | 65 | // Mutation returns the UserMutation object of the builder. 66 | func (uu *UserUpdate) Mutation() *UserMutation { 67 | return uu.mutation 68 | } 69 | 70 | // ClearTodos clears all "todos" edges to the Todo entity. 71 | func (uu *UserUpdate) ClearTodos() *UserUpdate { 72 | uu.mutation.ClearTodos() 73 | return uu 74 | } 75 | 76 | // RemoveTodoIDs removes the "todos" edge to Todo entities by IDs. 77 | func (uu *UserUpdate) RemoveTodoIDs(ids ...ulid.ID) *UserUpdate { 78 | uu.mutation.RemoveTodoIDs(ids...) 79 | return uu 80 | } 81 | 82 | // RemoveTodos removes "todos" edges to Todo entities. 83 | func (uu *UserUpdate) RemoveTodos(t ...*Todo) *UserUpdate { 84 | ids := make([]ulid.ID, len(t)) 85 | for i := range t { 86 | ids[i] = t[i].ID 87 | } 88 | return uu.RemoveTodoIDs(ids...) 89 | } 90 | 91 | // Save executes the query and returns the number of nodes affected by the update operation. 92 | func (uu *UserUpdate) Save(ctx context.Context) (int, error) { 93 | var ( 94 | err error 95 | affected int 96 | ) 97 | if len(uu.hooks) == 0 { 98 | if err = uu.check(); err != nil { 99 | return 0, err 100 | } 101 | affected, err = uu.sqlSave(ctx) 102 | } else { 103 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 104 | mutation, ok := m.(*UserMutation) 105 | if !ok { 106 | return nil, fmt.Errorf("unexpected mutation type %T", m) 107 | } 108 | if err = uu.check(); err != nil { 109 | return 0, err 110 | } 111 | uu.mutation = mutation 112 | affected, err = uu.sqlSave(ctx) 113 | mutation.done = true 114 | return affected, err 115 | }) 116 | for i := len(uu.hooks) - 1; i >= 0; i-- { 117 | if uu.hooks[i] == nil { 118 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 119 | } 120 | mut = uu.hooks[i](mut) 121 | } 122 | if _, err := mut.Mutate(ctx, uu.mutation); err != nil { 123 | return 0, err 124 | } 125 | } 126 | return affected, err 127 | } 128 | 129 | // SaveX is like Save, but panics if an error occurs. 130 | func (uu *UserUpdate) SaveX(ctx context.Context) int { 131 | affected, err := uu.Save(ctx) 132 | if err != nil { 133 | panic(err) 134 | } 135 | return affected 136 | } 137 | 138 | // Exec executes the query. 139 | func (uu *UserUpdate) Exec(ctx context.Context) error { 140 | _, err := uu.Save(ctx) 141 | return err 142 | } 143 | 144 | // ExecX is like Exec, but panics if an error occurs. 145 | func (uu *UserUpdate) ExecX(ctx context.Context) { 146 | if err := uu.Exec(ctx); err != nil { 147 | panic(err) 148 | } 149 | } 150 | 151 | // check runs all checks and user-defined validators on the builder. 152 | func (uu *UserUpdate) check() error { 153 | if v, ok := uu.mutation.Name(); ok { 154 | if err := user.NameValidator(v); err != nil { 155 | return &ValidationError{Name: "name", err: fmt.Errorf("ent: validator failed for field \"name\": %w", err)} 156 | } 157 | } 158 | if v, ok := uu.mutation.Age(); ok { 159 | if err := user.AgeValidator(v); err != nil { 160 | return &ValidationError{Name: "age", err: fmt.Errorf("ent: validator failed for field \"age\": %w", err)} 161 | } 162 | } 163 | return nil 164 | } 165 | 166 | func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { 167 | _spec := &sqlgraph.UpdateSpec{ 168 | Node: &sqlgraph.NodeSpec{ 169 | Table: user.Table, 170 | Columns: user.Columns, 171 | ID: &sqlgraph.FieldSpec{ 172 | Type: field.TypeString, 173 | Column: user.FieldID, 174 | }, 175 | }, 176 | } 177 | if ps := uu.mutation.predicates; len(ps) > 0 { 178 | _spec.Predicate = func(selector *sql.Selector) { 179 | for i := range ps { 180 | ps[i](selector) 181 | } 182 | } 183 | } 184 | if value, ok := uu.mutation.Name(); ok { 185 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 186 | Type: field.TypeString, 187 | Value: value, 188 | Column: user.FieldName, 189 | }) 190 | } 191 | if value, ok := uu.mutation.Age(); ok { 192 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 193 | Type: field.TypeInt, 194 | Value: value, 195 | Column: user.FieldAge, 196 | }) 197 | } 198 | if value, ok := uu.mutation.AddedAge(); ok { 199 | _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ 200 | Type: field.TypeInt, 201 | Value: value, 202 | Column: user.FieldAge, 203 | }) 204 | } 205 | if uu.mutation.TodosCleared() { 206 | edge := &sqlgraph.EdgeSpec{ 207 | Rel: sqlgraph.O2M, 208 | Inverse: false, 209 | Table: user.TodosTable, 210 | Columns: []string{user.TodosColumn}, 211 | Bidi: false, 212 | Target: &sqlgraph.EdgeTarget{ 213 | IDSpec: &sqlgraph.FieldSpec{ 214 | Type: field.TypeString, 215 | Column: todo.FieldID, 216 | }, 217 | }, 218 | } 219 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 220 | } 221 | if nodes := uu.mutation.RemovedTodosIDs(); len(nodes) > 0 && !uu.mutation.TodosCleared() { 222 | edge := &sqlgraph.EdgeSpec{ 223 | Rel: sqlgraph.O2M, 224 | Inverse: false, 225 | Table: user.TodosTable, 226 | Columns: []string{user.TodosColumn}, 227 | Bidi: false, 228 | Target: &sqlgraph.EdgeTarget{ 229 | IDSpec: &sqlgraph.FieldSpec{ 230 | Type: field.TypeString, 231 | Column: todo.FieldID, 232 | }, 233 | }, 234 | } 235 | for _, k := range nodes { 236 | edge.Target.Nodes = append(edge.Target.Nodes, k) 237 | } 238 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 239 | } 240 | if nodes := uu.mutation.TodosIDs(); len(nodes) > 0 { 241 | edge := &sqlgraph.EdgeSpec{ 242 | Rel: sqlgraph.O2M, 243 | Inverse: false, 244 | Table: user.TodosTable, 245 | Columns: []string{user.TodosColumn}, 246 | Bidi: false, 247 | Target: &sqlgraph.EdgeTarget{ 248 | IDSpec: &sqlgraph.FieldSpec{ 249 | Type: field.TypeString, 250 | Column: todo.FieldID, 251 | }, 252 | }, 253 | } 254 | for _, k := range nodes { 255 | edge.Target.Nodes = append(edge.Target.Nodes, k) 256 | } 257 | _spec.Edges.Add = append(_spec.Edges.Add, edge) 258 | } 259 | if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { 260 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 261 | err = &NotFoundError{user.Label} 262 | } else if sqlgraph.IsConstraintError(err) { 263 | err = &ConstraintError{err.Error(), err} 264 | } 265 | return 0, err 266 | } 267 | return n, nil 268 | } 269 | 270 | // UserUpdateOne is the builder for updating a single User entity. 271 | type UserUpdateOne struct { 272 | config 273 | fields []string 274 | hooks []Hook 275 | mutation *UserMutation 276 | } 277 | 278 | // SetName sets the "name" field. 279 | func (uuo *UserUpdateOne) SetName(s string) *UserUpdateOne { 280 | uuo.mutation.SetName(s) 281 | return uuo 282 | } 283 | 284 | // SetAge sets the "age" field. 285 | func (uuo *UserUpdateOne) SetAge(i int) *UserUpdateOne { 286 | uuo.mutation.ResetAge() 287 | uuo.mutation.SetAge(i) 288 | return uuo 289 | } 290 | 291 | // AddAge adds i to the "age" field. 292 | func (uuo *UserUpdateOne) AddAge(i int) *UserUpdateOne { 293 | uuo.mutation.AddAge(i) 294 | return uuo 295 | } 296 | 297 | // AddTodoIDs adds the "todos" edge to the Todo entity by IDs. 298 | func (uuo *UserUpdateOne) AddTodoIDs(ids ...ulid.ID) *UserUpdateOne { 299 | uuo.mutation.AddTodoIDs(ids...) 300 | return uuo 301 | } 302 | 303 | // AddTodos adds the "todos" edges to the Todo entity. 304 | func (uuo *UserUpdateOne) AddTodos(t ...*Todo) *UserUpdateOne { 305 | ids := make([]ulid.ID, len(t)) 306 | for i := range t { 307 | ids[i] = t[i].ID 308 | } 309 | return uuo.AddTodoIDs(ids...) 310 | } 311 | 312 | // Mutation returns the UserMutation object of the builder. 313 | func (uuo *UserUpdateOne) Mutation() *UserMutation { 314 | return uuo.mutation 315 | } 316 | 317 | // ClearTodos clears all "todos" edges to the Todo entity. 318 | func (uuo *UserUpdateOne) ClearTodos() *UserUpdateOne { 319 | uuo.mutation.ClearTodos() 320 | return uuo 321 | } 322 | 323 | // RemoveTodoIDs removes the "todos" edge to Todo entities by IDs. 324 | func (uuo *UserUpdateOne) RemoveTodoIDs(ids ...ulid.ID) *UserUpdateOne { 325 | uuo.mutation.RemoveTodoIDs(ids...) 326 | return uuo 327 | } 328 | 329 | // RemoveTodos removes "todos" edges to Todo entities. 330 | func (uuo *UserUpdateOne) RemoveTodos(t ...*Todo) *UserUpdateOne { 331 | ids := make([]ulid.ID, len(t)) 332 | for i := range t { 333 | ids[i] = t[i].ID 334 | } 335 | return uuo.RemoveTodoIDs(ids...) 336 | } 337 | 338 | // Select allows selecting one or more fields (columns) of the returned entity. 339 | // The default is selecting all fields defined in the entity schema. 340 | func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne { 341 | uuo.fields = append([]string{field}, fields...) 342 | return uuo 343 | } 344 | 345 | // Save executes the query and returns the updated User entity. 346 | func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { 347 | var ( 348 | err error 349 | node *User 350 | ) 351 | if len(uuo.hooks) == 0 { 352 | if err = uuo.check(); err != nil { 353 | return nil, err 354 | } 355 | node, err = uuo.sqlSave(ctx) 356 | } else { 357 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 358 | mutation, ok := m.(*UserMutation) 359 | if !ok { 360 | return nil, fmt.Errorf("unexpected mutation type %T", m) 361 | } 362 | if err = uuo.check(); err != nil { 363 | return nil, err 364 | } 365 | uuo.mutation = mutation 366 | node, err = uuo.sqlSave(ctx) 367 | mutation.done = true 368 | return node, err 369 | }) 370 | for i := len(uuo.hooks) - 1; i >= 0; i-- { 371 | if uuo.hooks[i] == nil { 372 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 373 | } 374 | mut = uuo.hooks[i](mut) 375 | } 376 | if _, err := mut.Mutate(ctx, uuo.mutation); err != nil { 377 | return nil, err 378 | } 379 | } 380 | return node, err 381 | } 382 | 383 | // SaveX is like Save, but panics if an error occurs. 384 | func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User { 385 | node, err := uuo.Save(ctx) 386 | if err != nil { 387 | panic(err) 388 | } 389 | return node 390 | } 391 | 392 | // Exec executes the query on the entity. 393 | func (uuo *UserUpdateOne) Exec(ctx context.Context) error { 394 | _, err := uuo.Save(ctx) 395 | return err 396 | } 397 | 398 | // ExecX is like Exec, but panics if an error occurs. 399 | func (uuo *UserUpdateOne) ExecX(ctx context.Context) { 400 | if err := uuo.Exec(ctx); err != nil { 401 | panic(err) 402 | } 403 | } 404 | 405 | // check runs all checks and user-defined validators on the builder. 406 | func (uuo *UserUpdateOne) check() error { 407 | if v, ok := uuo.mutation.Name(); ok { 408 | if err := user.NameValidator(v); err != nil { 409 | return &ValidationError{Name: "name", err: fmt.Errorf("ent: validator failed for field \"name\": %w", err)} 410 | } 411 | } 412 | if v, ok := uuo.mutation.Age(); ok { 413 | if err := user.AgeValidator(v); err != nil { 414 | return &ValidationError{Name: "age", err: fmt.Errorf("ent: validator failed for field \"age\": %w", err)} 415 | } 416 | } 417 | return nil 418 | } 419 | 420 | func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { 421 | _spec := &sqlgraph.UpdateSpec{ 422 | Node: &sqlgraph.NodeSpec{ 423 | Table: user.Table, 424 | Columns: user.Columns, 425 | ID: &sqlgraph.FieldSpec{ 426 | Type: field.TypeString, 427 | Column: user.FieldID, 428 | }, 429 | }, 430 | } 431 | id, ok := uuo.mutation.ID() 432 | if !ok { 433 | return nil, &ValidationError{Name: "ID", err: fmt.Errorf("missing User.ID for update")} 434 | } 435 | _spec.Node.ID.Value = id 436 | if fields := uuo.fields; len(fields) > 0 { 437 | _spec.Node.Columns = make([]string, 0, len(fields)) 438 | _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) 439 | for _, f := range fields { 440 | if !user.ValidColumn(f) { 441 | return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} 442 | } 443 | if f != user.FieldID { 444 | _spec.Node.Columns = append(_spec.Node.Columns, f) 445 | } 446 | } 447 | } 448 | if ps := uuo.mutation.predicates; len(ps) > 0 { 449 | _spec.Predicate = func(selector *sql.Selector) { 450 | for i := range ps { 451 | ps[i](selector) 452 | } 453 | } 454 | } 455 | if value, ok := uuo.mutation.Name(); ok { 456 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 457 | Type: field.TypeString, 458 | Value: value, 459 | Column: user.FieldName, 460 | }) 461 | } 462 | if value, ok := uuo.mutation.Age(); ok { 463 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 464 | Type: field.TypeInt, 465 | Value: value, 466 | Column: user.FieldAge, 467 | }) 468 | } 469 | if value, ok := uuo.mutation.AddedAge(); ok { 470 | _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ 471 | Type: field.TypeInt, 472 | Value: value, 473 | Column: user.FieldAge, 474 | }) 475 | } 476 | if uuo.mutation.TodosCleared() { 477 | edge := &sqlgraph.EdgeSpec{ 478 | Rel: sqlgraph.O2M, 479 | Inverse: false, 480 | Table: user.TodosTable, 481 | Columns: []string{user.TodosColumn}, 482 | Bidi: false, 483 | Target: &sqlgraph.EdgeTarget{ 484 | IDSpec: &sqlgraph.FieldSpec{ 485 | Type: field.TypeString, 486 | Column: todo.FieldID, 487 | }, 488 | }, 489 | } 490 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 491 | } 492 | if nodes := uuo.mutation.RemovedTodosIDs(); len(nodes) > 0 && !uuo.mutation.TodosCleared() { 493 | edge := &sqlgraph.EdgeSpec{ 494 | Rel: sqlgraph.O2M, 495 | Inverse: false, 496 | Table: user.TodosTable, 497 | Columns: []string{user.TodosColumn}, 498 | Bidi: false, 499 | Target: &sqlgraph.EdgeTarget{ 500 | IDSpec: &sqlgraph.FieldSpec{ 501 | Type: field.TypeString, 502 | Column: todo.FieldID, 503 | }, 504 | }, 505 | } 506 | for _, k := range nodes { 507 | edge.Target.Nodes = append(edge.Target.Nodes, k) 508 | } 509 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 510 | } 511 | if nodes := uuo.mutation.TodosIDs(); len(nodes) > 0 { 512 | edge := &sqlgraph.EdgeSpec{ 513 | Rel: sqlgraph.O2M, 514 | Inverse: false, 515 | Table: user.TodosTable, 516 | Columns: []string{user.TodosColumn}, 517 | Bidi: false, 518 | Target: &sqlgraph.EdgeTarget{ 519 | IDSpec: &sqlgraph.FieldSpec{ 520 | Type: field.TypeString, 521 | Column: todo.FieldID, 522 | }, 523 | }, 524 | } 525 | for _, k := range nodes { 526 | edge.Target.Nodes = append(edge.Target.Nodes, k) 527 | } 528 | _spec.Edges.Add = append(_spec.Edges.Add, edge) 529 | } 530 | _node = &User{config: uuo.config} 531 | _spec.Assign = _node.assignValues 532 | _spec.ScanValues = _node.scanValues 533 | if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil { 534 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 535 | err = &NotFoundError{user.Label} 536 | } else if sqlgraph.IsConstraintError(err) { 537 | err = &ConstraintError{err.Error(), err} 538 | } 539 | return nil, err 540 | } 541 | return _node, nil 542 | } 543 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang-clean-architecture-ent-gqlgen 2 | 3 | go 1.17 4 | 5 | require ( 6 | entgo.io/contrib v0.2.0 7 | entgo.io/ent v0.9.2-0.20210821141344-368a8f7a2e9a 8 | github.com/99designs/gqlgen v0.14.0 9 | github.com/gavv/httpexpect/v2 v2.3.1 10 | github.com/go-sql-driver/mysql v1.6.0 11 | github.com/hashicorp/go-multierror v1.1.1 12 | github.com/labstack/echo/v4 v4.6.1 13 | github.com/oklog/ulid/v2 v2.0.2 14 | github.com/pkg/errors v0.9.1 15 | github.com/spf13/viper v1.9.0 16 | github.com/stretchr/testify v1.7.0 17 | github.com/vektah/gqlparser/v2 v2.2.0 18 | github.com/vmihailenco/msgpack/v5 v5.0.0-beta.9 19 | ) 20 | 21 | require ( 22 | github.com/agnivade/levenshtein v1.1.0 // indirect 23 | github.com/ajg/form v1.5.1 // indirect 24 | github.com/andybalholm/brotli v1.0.2 // indirect 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/fatih/structs v1.0.0 // indirect 27 | github.com/fsnotify/fsnotify v1.5.1 // indirect 28 | github.com/go-openapi/inflect v0.19.0 // indirect 29 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 30 | github.com/google/go-querystring v1.0.0 // indirect 31 | github.com/google/uuid v1.3.0 // indirect 32 | github.com/gorilla/websocket v1.4.2 // indirect 33 | github.com/graphql-go/graphql v0.7.10-0.20210411022516-8a92e977c10b // indirect 34 | github.com/hashicorp/errwrap v1.1.0 // indirect 35 | github.com/hashicorp/golang-lru v0.5.4 // indirect 36 | github.com/hashicorp/hcl v1.0.0 // indirect 37 | github.com/imkira/go-interpol v1.0.0 // indirect 38 | github.com/klauspost/compress v1.12.2 // indirect 39 | github.com/labstack/gommon v0.3.0 // indirect 40 | github.com/magiconair/properties v1.8.5 // indirect 41 | github.com/mattn/go-colorable v0.1.8 // indirect 42 | github.com/mattn/go-isatty v0.0.14 // indirect 43 | github.com/mitchellh/mapstructure v1.4.2 // indirect 44 | github.com/pelletier/go-toml v1.9.4 // indirect 45 | github.com/pmezard/go-difflib v1.0.0 // indirect 46 | github.com/sergi/go-diff v1.1.0 // indirect 47 | github.com/spf13/afero v1.6.0 // indirect 48 | github.com/spf13/cast v1.4.1 // indirect 49 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 50 | github.com/spf13/pflag v1.0.5 // indirect 51 | github.com/subosito/gotenv v1.2.0 // indirect 52 | github.com/valyala/bytebufferpool v1.0.0 // indirect 53 | github.com/valyala/fasthttp v1.27.0 // indirect 54 | github.com/valyala/fasttemplate v1.2.1 // indirect 55 | github.com/vmihailenco/tagparser v0.1.2 // indirect 56 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect 57 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 58 | github.com/xeipuuv/gojsonschema v1.1.0 // indirect 59 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect 60 | github.com/yudai/gojsondiff v1.0.0 // indirect 61 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 62 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 63 | golang.org/x/mod v0.5.1 // indirect 64 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect 65 | golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 // indirect 66 | golang.org/x/text v0.3.7 // indirect 67 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect 68 | golang.org/x/tools v0.1.7 // indirect 69 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 70 | gopkg.in/ini.v1 v1.63.2 // indirect 71 | gopkg.in/yaml.v2 v2.4.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 73 | moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /gqlgen.yml: -------------------------------------------------------------------------------- 1 | # Where are all the schema files located? globs are supported eg src/**/*.graphqls 2 | schema: 3 | - graph/**/*.graphqls 4 | 5 | # Where should the generated server code go? 6 | exec: 7 | filename: graph/generated/generated.go 8 | package: generated 9 | 10 | # Uncomment to enable federation 11 | # federation: 12 | # filename: graph/generated/federation.go 13 | # package: generated 14 | 15 | # Where should any generated models go? 16 | #model: 17 | # filename: graph/model/models_gen.go 18 | # package: model 19 | 20 | # Where should the resolver implementations go? 21 | resolver: 22 | layout: follow-schema 23 | dir: pkg/adapter/resolver 24 | package: resolver 25 | 26 | # Optional: turn on use `gqlgen:"fieldName"` tags in your models 27 | # struct_tag: json 28 | 29 | # Optional: turn on to use []Thing instead of []*Thing 30 | # omit_slice_element_pointers: false 31 | 32 | # Optional: set to speed up generation time by not performing a final validation pass. 33 | # skip_validation: true 34 | 35 | # gqlgen will search for any type names in the schema in these go packages 36 | # if they match it will use them, otherwise it will generate them. 37 | autobind: 38 | - golang-clean-architecture-ent-gqlgen/pkg/entity/model 39 | - golang-clean-architecture-ent-gqlgen/ent 40 | 41 | # This section declares type mapping between the GraphQL and go type systems 42 | # 43 | # The first line in each type will be used as defaults for resolver arguments and 44 | # modelgen, the others will be allowed when binding to fields. Configure them to 45 | # your liking 46 | models: 47 | ID: 48 | model: 49 | - golang-clean-architecture-ent-gqlgen/pkg/entity/model.ID 50 | Int: 51 | model: 52 | - github.com/99designs/gqlgen/graphql.Int 53 | - github.com/99designs/gqlgen/graphql.Int64 54 | - github.com/99designs/gqlgen/graphql.Int32 55 | Node: 56 | model: 57 | - golang-clean-architecture-ent-gqlgen/pkg/entity/model.Node 58 | TodoStatus: 59 | model: 60 | - golang-clean-architecture-ent-gqlgen/ent/todo.Status 61 | -------------------------------------------------------------------------------- /graph/ent.graphqls: -------------------------------------------------------------------------------- 1 | """ 2 | TodoWhereInput is used for filtering Todo objects. 3 | Input was generated by ent. 4 | """ 5 | input TodoWhereInput { 6 | not: TodoWhereInput 7 | and: [TodoWhereInput!] 8 | or: [TodoWhereInput!] 9 | 10 | """user_id field predicates""" 11 | userID: ID 12 | userIDNEQ: ID 13 | userIDIn: [ID!] 14 | userIDNotIn: [ID!] 15 | userIDGT: ID 16 | userIDGTE: ID 17 | userIDLT: ID 18 | userIDLTE: ID 19 | userIDContains: ID 20 | userIDHasPrefix: ID 21 | userIDHasSuffix: ID 22 | userIDIsNil: Boolean 23 | userIDNotNil: Boolean 24 | userIDEqualFold: ID 25 | userIDContainsFold: ID 26 | 27 | """name field predicates""" 28 | name: String 29 | nameNEQ: String 30 | nameIn: [String!] 31 | nameNotIn: [String!] 32 | nameGT: String 33 | nameGTE: String 34 | nameLT: String 35 | nameLTE: String 36 | nameContains: String 37 | nameHasPrefix: String 38 | nameHasSuffix: String 39 | nameEqualFold: String 40 | nameContainsFold: String 41 | 42 | """status field predicates""" 43 | status: TodoStatus 44 | statusNEQ: TodoStatus 45 | statusIn: [TodoStatus!] 46 | statusNotIn: [TodoStatus!] 47 | 48 | """priority field predicates""" 49 | priority: Int 50 | priorityNEQ: Int 51 | priorityIn: [Int!] 52 | priorityNotIn: [Int!] 53 | priorityGT: Int 54 | priorityGTE: Int 55 | priorityLT: Int 56 | priorityLTE: Int 57 | 58 | """created_at field predicates""" 59 | createdAt: Time 60 | createdAtNEQ: Time 61 | createdAtIn: [Time!] 62 | createdAtNotIn: [Time!] 63 | createdAtGT: Time 64 | createdAtGTE: Time 65 | createdAtLT: Time 66 | createdAtLTE: Time 67 | 68 | """updated_at field predicates""" 69 | updatedAt: Time 70 | updatedAtNEQ: Time 71 | updatedAtIn: [Time!] 72 | updatedAtNotIn: [Time!] 73 | updatedAtGT: Time 74 | updatedAtGTE: Time 75 | updatedAtLT: Time 76 | updatedAtLTE: Time 77 | 78 | """id field predicates""" 79 | id: ID 80 | idNEQ: ID 81 | idIn: [ID!] 82 | idNotIn: [ID!] 83 | idGT: ID 84 | idGTE: ID 85 | idLT: ID 86 | idLTE: ID 87 | 88 | """user edge predicates""" 89 | hasUser: Boolean 90 | hasUserWith: [UserWhereInput!] 91 | } 92 | 93 | """ 94 | UserWhereInput is used for filtering User objects. 95 | Input was generated by ent. 96 | """ 97 | input UserWhereInput { 98 | not: UserWhereInput 99 | and: [UserWhereInput!] 100 | or: [UserWhereInput!] 101 | 102 | """name field predicates""" 103 | name: String 104 | nameNEQ: String 105 | nameIn: [String!] 106 | nameNotIn: [String!] 107 | nameGT: String 108 | nameGTE: String 109 | nameLT: String 110 | nameLTE: String 111 | nameContains: String 112 | nameHasPrefix: String 113 | nameHasSuffix: String 114 | nameEqualFold: String 115 | nameContainsFold: String 116 | 117 | """age field predicates""" 118 | age: Int 119 | ageNEQ: Int 120 | ageIn: [Int!] 121 | ageNotIn: [Int!] 122 | ageGT: Int 123 | ageGTE: Int 124 | ageLT: Int 125 | ageLTE: Int 126 | 127 | """created_at field predicates""" 128 | createdAt: Time 129 | createdAtNEQ: Time 130 | createdAtIn: [Time!] 131 | createdAtNotIn: [Time!] 132 | createdAtGT: Time 133 | createdAtGTE: Time 134 | createdAtLT: Time 135 | createdAtLTE: Time 136 | 137 | """updated_at field predicates""" 138 | updatedAt: Time 139 | updatedAtNEQ: Time 140 | updatedAtIn: [Time!] 141 | updatedAtNotIn: [Time!] 142 | updatedAtGT: Time 143 | updatedAtGTE: Time 144 | updatedAtLT: Time 145 | updatedAtLTE: Time 146 | 147 | """id field predicates""" 148 | id: ID 149 | idNEQ: ID 150 | idIn: [ID!] 151 | idNotIn: [ID!] 152 | idGT: ID 153 | idGTE: ID 154 | idLT: ID 155 | idLTE: ID 156 | 157 | """todos edge predicates""" 158 | hasTodos: Boolean 159 | hasTodosWith: [TodoWhereInput!] 160 | } 161 | -------------------------------------------------------------------------------- /graph/schema.graphqls: -------------------------------------------------------------------------------- 1 | scalar Cursor 2 | scalar Time 3 | 4 | type PageInfo { 5 | hasNextPage: Boolean! 6 | hasPreviousPage: Boolean! 7 | startCursor: Cursor 8 | endCursor: Cursor 9 | } 10 | 11 | interface Node { 12 | id: ID! 13 | } 14 | 15 | type Query { 16 | node(id: ID!): Node 17 | } 18 | 19 | type Mutation 20 | -------------------------------------------------------------------------------- /graph/todo.graphqls: -------------------------------------------------------------------------------- 1 | enum TodoStatus { 2 | IN_PROGRESS 3 | COMPLETED 4 | } 5 | 6 | type Todo { 7 | id: ID! 8 | name: String! 9 | status: TodoStatus! 10 | priority: Int! 11 | userID: ID 12 | createdAt: String! 13 | updatedAt: String! 14 | } 15 | input CreateTodoInput { 16 | name: String! 17 | status: TodoStatus = IN_PROGRESS 18 | priority: Int! 19 | userID: ID! 20 | } 21 | input UpdateTodoInput { 22 | id: ID! 23 | name: String 24 | status: TodoStatus 25 | priority: Int 26 | userID: ID 27 | } 28 | 29 | extend type Query { 30 | Todo(id: ID): Todo 31 | Todos: [Todo!]! 32 | } 33 | 34 | extend type Mutation { 35 | createTodo(input: CreateTodoInput!): Todo! 36 | updateTodo(input: UpdateTodoInput!): Todo! 37 | } 38 | -------------------------------------------------------------------------------- /graph/user.graphqls: -------------------------------------------------------------------------------- 1 | type User implements Node { 2 | id: ID! 3 | name: String! 4 | age: Int! 5 | todos: [Todo!]! 6 | createdAt: String! 7 | updatedAt: String! 8 | } 9 | 10 | type UserConnection { 11 | totalCount: Int! 12 | pageInfo: PageInfo! 13 | edges: [UserEdge] 14 | } 15 | type UserEdge { 16 | node: User 17 | cursor: Cursor! 18 | } 19 | 20 | input CreateUserInput { 21 | name: String! 22 | age: Int! 23 | } 24 | input UpdateUserInput { 25 | id: ID! 26 | name: String 27 | age: Int 28 | } 29 | 30 | extend type Query { 31 | user(id: ID): User 32 | users(after: Cursor, first: Int, before: Cursor, last: Int, where: UserWhereInput): UserConnection 33 | } 34 | 35 | 36 | extend type Mutation { 37 | createUser(input: CreateUserInput!): User! 38 | createUserWithTodo(input: CreateUserInput!): User! 39 | updateUser(input: UpdateUserInput!): User! 40 | } 41 | -------------------------------------------------------------------------------- /pkg/adapter/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | // Controller holds the controllers for the entire across application 4 | type Controller struct { 5 | User interface{ User } 6 | Todo interface{ Todo } 7 | } 8 | -------------------------------------------------------------------------------- /pkg/adapter/controller/todo.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/usecase" 7 | ) 8 | 9 | // Todo is an interface of controller 10 | type Todo interface { 11 | Get(ctx context.Context, id *model.ID) (*model.Todo, error) 12 | List(ctx context.Context) ([]*model.Todo, error) 13 | Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) 14 | Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) 15 | } 16 | 17 | type testTodo struct { 18 | testTodoUsecase usecase.Todo 19 | } 20 | 21 | // NewTodoController generates test user controller 22 | func NewTodoController(tu usecase.Todo) Todo { 23 | return &testTodo{ 24 | testTodoUsecase: tu, 25 | } 26 | } 27 | 28 | func (t *testTodo) Get(ctx context.Context, id *model.ID) (*model.Todo, error) { 29 | return t.testTodoUsecase.Get(ctx, id) 30 | } 31 | 32 | func (t *testTodo) List(ctx context.Context) ([]*model.Todo, error) { 33 | return t.testTodoUsecase.List(ctx) 34 | } 35 | 36 | func (t *testTodo) Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) { 37 | return t.testTodoUsecase.Create(ctx, input) 38 | } 39 | 40 | func (t *testTodo) Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) { 41 | return t.testTodoUsecase.Update(ctx, input) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/adapter/controller/user.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/usecase" 7 | ) 8 | 9 | type user struct { 10 | userUsecase usecase.User 11 | } 12 | 13 | // User of interface 14 | type User interface { 15 | Get(ctx context.Context, id *model.ID) (*model.User, error) 16 | List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) 17 | Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) 18 | CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) 19 | Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) 20 | } 21 | 22 | // NewUserController returns user controller 23 | func NewUserController(uu usecase.User) User { 24 | return &user{userUsecase: uu} 25 | } 26 | 27 | func (u *user) Get(ctx context.Context, id *model.ID) (*model.User, error) { 28 | return u.userUsecase.Get(ctx, id) 29 | } 30 | 31 | func (u *user) List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) { 32 | return u.userUsecase.List(ctx, after, first, before, last, where) 33 | } 34 | 35 | func (u *user) Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 36 | return u.userUsecase.Create(ctx, input) 37 | } 38 | 39 | func (u *user) CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 40 | return u.userUsecase.CreateWithTodo(ctx, input) 41 | } 42 | 43 | func (u *user) Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) { 44 | return u.userUsecase.Update(ctx, input) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/adapter/handler/error.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 7 | 8 | "github.com/99designs/gqlgen/graphql" 9 | "github.com/vektah/gqlparser/v2/gqlerror" 10 | ) 11 | 12 | // HandleError tasks any errors and add it to the graphql errors. 13 | // It is basically intended to be used at the adapter/resolver layer 14 | func HandleError(ctx context.Context, err error) error { 15 | var extendedError interface{ Extensions() map[string]interface{} } 16 | 17 | for err != nil { 18 | u, ok := err.(interface { 19 | Unwrap() error 20 | }) 21 | if !ok { 22 | break 23 | } 24 | 25 | // Skip when its stack strace 26 | if model.IsStackTrace(err) { 27 | err = u.Unwrap() 28 | continue 29 | } 30 | 31 | // Skip when it's not the standard error type 32 | if !model.IsError(err) { 33 | err = u.Unwrap() 34 | continue 35 | } 36 | 37 | gqlerr := &gqlerror.Error{ 38 | Path: graphql.GetPath(ctx), 39 | Message: err.Error(), 40 | } 41 | if errors.As(err, &extendedError) { 42 | gqlerr.Extensions = extendedError.Extensions() 43 | } 44 | 45 | graphql.AddError(ctx, gqlerr) 46 | 47 | err = u.Unwrap() 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/adapter/repository/todo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/ent/todo" 7 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 8 | ur "golang-clean-architecture-ent-gqlgen/pkg/usecase/repository" 9 | ) 10 | 11 | type todoRepository struct { 12 | client *ent.Client 13 | } 14 | 15 | // NewTodoRepository generates user repository 16 | func NewTodoRepository(client *ent.Client) ur.Todo { 17 | return &todoRepository{client: client} 18 | } 19 | 20 | func (r *todoRepository) Get(ctx context.Context, id *model.ID) (*model.Todo, error) { 21 | q := r.client.Todo.Query() 22 | if id != nil { 23 | q.Where(todo.IDEQ(*id)) 24 | } 25 | 26 | u, err := q.Only(ctx) 27 | 28 | if err != nil { 29 | if ent.IsNotSingular(err) { 30 | return nil, model.NewNotFoundError(err, map[string]interface{}{ 31 | "id": id, 32 | }) 33 | } 34 | if ent.IsNotFound(err) { 35 | return nil, nil 36 | } 37 | return nil, model.NewDBError(err) 38 | } 39 | 40 | return u, nil 41 | } 42 | 43 | func (r *todoRepository) List(ctx context.Context) ([]*model.Todo, error) { 44 | ts, err := r.client. 45 | Todo. 46 | Query(). 47 | All(ctx) 48 | if err != nil { 49 | return nil, model.NewDBError(err) 50 | } 51 | return ts, nil 52 | } 53 | 54 | func (r *todoRepository) Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) { 55 | u, err := r.client. 56 | Todo. 57 | Create(). 58 | SetInput(input). 59 | Save(ctx) 60 | 61 | if err != nil { 62 | return nil, model.NewDBError(err) 63 | } 64 | 65 | return u, nil 66 | } 67 | 68 | func (r *todoRepository) Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) { 69 | u, err := r.client. 70 | Todo.UpdateOneID(input.ID). 71 | SetInput(input). 72 | Save(ctx) 73 | 74 | if err != nil { 75 | if ent.IsNotFound(err) { 76 | return nil, model.NewNotFoundError(err, input.ID) 77 | } 78 | 79 | return nil, model.NewDBError(err) 80 | } 81 | 82 | return u, nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/adapter/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/ent/user" 7 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 8 | usecaseRepository "golang-clean-architecture-ent-gqlgen/pkg/usecase/repository" 9 | ) 10 | 11 | type userRepository struct { 12 | client *ent.Client 13 | } 14 | 15 | // NewUserRepository is specific implementation of the interface 16 | func NewUserRepository(client *ent.Client) usecaseRepository.User { 17 | return &userRepository{client: client} 18 | } 19 | 20 | func (r *userRepository) Get(ctx context.Context, id *model.ID) (*model.User, error) { 21 | u, err := r.client.User.Query().Where(user.IDEQ(*id)).Only(ctx) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return u, nil 26 | } 27 | 28 | func (r *userRepository) List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) { 29 | us, err := r.client. 30 | User. 31 | Query(). 32 | Paginate(ctx, after, first, before, last, ent.WithUserFilter(where.Filter)) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return us, nil 37 | } 38 | 39 | func (r *userRepository) Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 40 | u, err := r.client.User.Create().SetInput(input).Save(ctx) 41 | if err != nil { 42 | return nil, model.NewDBError(err) 43 | } 44 | return u, nil 45 | } 46 | 47 | func (r *userRepository) CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 48 | client := WithTransactionalMutation(ctx) 49 | 50 | todo, err := client. 51 | Todo. 52 | Create(). 53 | Save(ctx) 54 | if err != nil { 55 | return nil, model.NewDBError(err) 56 | } 57 | 58 | u, err := client.User. 59 | Create(). 60 | SetInput(input). 61 | AddTodos(todo). 62 | Save(ctx) 63 | if err != nil { 64 | return nil, model.NewDBError(err) 65 | } 66 | 67 | return u, nil 68 | } 69 | 70 | func (r *userRepository) Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) { 71 | u, err := r.client.User.UpdateOneID(input.ID).SetInput(input).Save(ctx) 72 | if err != nil { 73 | return nil, model.NewDBError(err) 74 | } 75 | return u, nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/adapter/repository/user_test.go: -------------------------------------------------------------------------------- 1 | package repository_test 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/repository" 7 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 8 | "golang-clean-architecture-ent-gqlgen/testutil" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func setup(t *testing.T) (client *ent.Client, teardown func()) { 15 | testutil.ReadConfig() 16 | c := testutil.NewDBClient(t) 17 | 18 | return c, func() { 19 | testutil.DropUser(t, c) 20 | defer c.Close() 21 | } 22 | } 23 | 24 | func TestUserRepository_List(t *testing.T) { 25 | t.Helper() 26 | 27 | client, teardown := setup(t) 28 | defer teardown() 29 | 30 | repo := repository.NewUserRepository(client) 31 | 32 | type args struct { 33 | ctx context.Context 34 | } 35 | 36 | tests := []struct { 37 | name string 38 | arrange func(t *testing.T) 39 | act func(ctx context.Context, t *testing.T) (uc *model.UserConnection, err error) 40 | assert func(t *testing.T, uc *model.UserConnection, err error) 41 | args struct { 42 | ctx context.Context 43 | } 44 | teardown func(t *testing.T) 45 | }{ 46 | { 47 | name: "It should get user's list", 48 | arrange: func(t *testing.T) { 49 | ctx := context.Background() 50 | _, err := client.User.Delete().Exec(ctx) 51 | if err != nil { 52 | t.Error(err) 53 | t.FailNow() 54 | } 55 | 56 | users := []struct { 57 | name string 58 | age int 59 | }{{name: "test", age: 10}, {name: "test2", age: 11}, {name: "test3", age: 12}} 60 | bulk := make([]*ent.UserCreate, len(users)) 61 | for i, u := range users { 62 | bulk[i] = client.User.Create().SetName(u.name).SetAge(u.age) 63 | } 64 | 65 | _, err = client.User. 66 | CreateBulk(bulk...). 67 | Save(ctx) 68 | if err != nil { 69 | t.Error(err) 70 | t.FailNow() 71 | } 72 | }, 73 | act: func(ctx context.Context, t *testing.T) (us *model.UserConnection, err error) { 74 | first := 5 75 | return repo.List(ctx, nil, &first, nil, nil, nil) 76 | }, 77 | assert: func(t *testing.T, got *model.UserConnection, err error) { 78 | assert.Nil(t, err) 79 | assert.Equal(t, 3, len(got.Edges)) 80 | }, 81 | args: args{ 82 | ctx: context.Background(), 83 | }, 84 | teardown: func(t *testing.T) { 85 | testutil.DropUser(t, client) 86 | }, 87 | }, 88 | } 89 | 90 | for _, tt := range tests { 91 | t.Run(tt.name, func(t *testing.T) { 92 | tt.arrange(t) 93 | got, err := tt.act(tt.args.ctx, t) 94 | tt.assert(t, got, err) 95 | tt.teardown(t) 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pkg/adapter/repository/with_transactional_mutation.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | ) 7 | 8 | // WithTransactionalMutation automatically wrap the GraphQL mutations with a database transaction. 9 | // This allows the ent.Client to commit at the end, or rollback the transaction in case of a GraphQL error. 10 | func WithTransactionalMutation(ctx context.Context) *ent.Client { 11 | return ent.FromContext(ctx) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/adapter/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | // This file will not be regenerated automatically. 4 | // 5 | // It serves as dependency injection for your app, add any dependencies you require here. 6 | 7 | import ( 8 | "golang-clean-architecture-ent-gqlgen/ent" 9 | "golang-clean-architecture-ent-gqlgen/graph/generated" 10 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 11 | 12 | "github.com/99designs/gqlgen/graphql" 13 | ) 14 | 15 | // Resolver is a context struct 16 | type Resolver struct { 17 | client *ent.Client 18 | controller controller.Controller 19 | } 20 | 21 | // NewSchema creates NewExecutableSchema 22 | func NewSchema(client *ent.Client, controller controller.Controller) graphql.ExecutableSchema { 23 | return generated.NewExecutableSchema(generated.Config{ 24 | Resolvers: &Resolver{ 25 | client: client, 26 | controller: controller, 27 | }, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/adapter/resolver/schema.resolvers.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | // This file will be automatically regenerated based on the schema, any resolver implementations 4 | // will be copied through when generating and any unknown code will be moved to the end. 5 | 6 | import ( 7 | "context" 8 | "golang-clean-architecture-ent-gqlgen/ent" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/graph/generated" 11 | ) 12 | 13 | func (r *queryResolver) Node(ctx context.Context, id ulid.ID) (ent.Noder, error) { 14 | n, err := r.client.Noder(ctx, id, ent.WithNodeType(ent.IDToType)) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return n, nil 19 | } 20 | 21 | // Mutation returns generated.MutationResolver implementation. 22 | func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } 23 | 24 | // Query returns generated.QueryResolver implementation. 25 | func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } 26 | 27 | type mutationResolver struct{ *Resolver } 28 | type queryResolver struct{ *Resolver } 29 | -------------------------------------------------------------------------------- /pkg/adapter/resolver/todo.resolvers.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | // This file will be automatically regenerated based on the schema, any resolver implementations 4 | // will be copied through when generating and any unknown code will be moved to the end. 5 | 6 | import ( 7 | "context" 8 | "golang-clean-architecture-ent-gqlgen/ent" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/graph/generated" 11 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/handler" 12 | "golang-clean-architecture-ent-gqlgen/pkg/util/datetime" 13 | ) 14 | 15 | func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) { 16 | t, err := r.controller.Todo.Create(ctx, input) 17 | if err != nil { 18 | return nil, handler.HandleError(ctx, err) 19 | } 20 | return t, nil 21 | } 22 | 23 | func (r *mutationResolver) UpdateTodo(ctx context.Context, input ent.UpdateTodoInput) (*ent.Todo, error) { 24 | t, err := r.controller.Todo.Update(ctx, input) 25 | if err != nil { 26 | return nil, handler.HandleError(ctx, err) 27 | } 28 | return t, nil 29 | } 30 | 31 | func (r *queryResolver) Todo(ctx context.Context, id *ulid.ID) (*ent.Todo, error) { 32 | t, err := r.controller.Todo.Get(ctx, id) 33 | if err != nil { 34 | return nil, handler.HandleError(ctx, err) 35 | } 36 | return t, nil 37 | } 38 | 39 | func (r *queryResolver) Todos(ctx context.Context) ([]*ent.Todo, error) { 40 | ts, err := r.controller.Todo.List(ctx) 41 | if err != nil { 42 | return nil, handler.HandleError(ctx, err) 43 | } 44 | return ts, nil 45 | } 46 | 47 | func (r *todoResolver) CreatedAt(ctx context.Context, obj *ent.Todo) (string, error) { 48 | return datetime.FormatDate(obj.CreatedAt), nil 49 | } 50 | 51 | func (r *todoResolver) UpdatedAt(ctx context.Context, obj *ent.Todo) (string, error) { 52 | return datetime.FormatDate(obj.UpdatedAt), nil 53 | } 54 | 55 | // Todo returns generated.TodoResolver implementation. 56 | func (r *Resolver) Todo() generated.TodoResolver { return &todoResolver{r} } 57 | 58 | type todoResolver struct{ *Resolver } 59 | -------------------------------------------------------------------------------- /pkg/adapter/resolver/user.resolvers.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | // This file will be automatically regenerated based on the schema, any resolver implementations 4 | // will be copied through when generating and any unknown code will be moved to the end. 5 | 6 | import ( 7 | "context" 8 | "golang-clean-architecture-ent-gqlgen/ent" 9 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 10 | "golang-clean-architecture-ent-gqlgen/graph/generated" 11 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/handler" 12 | "golang-clean-architecture-ent-gqlgen/pkg/util/datetime" 13 | ) 14 | 15 | func (r *mutationResolver) CreateUser(ctx context.Context, input ent.CreateUserInput) (*ent.User, error) { 16 | u, err := r.controller.User.Create(ctx, input) 17 | if err != nil { 18 | return nil, handler.HandleError(ctx, err) 19 | } 20 | return u, nil 21 | } 22 | 23 | func (r *mutationResolver) CreateUserWithTodo(ctx context.Context, input ent.CreateUserInput) (*ent.User, error) { 24 | u, err := r.controller.User.CreateWithTodo(ctx, input) 25 | if err != nil { 26 | return nil, handler.HandleError(ctx, err) 27 | } 28 | return u, nil 29 | } 30 | 31 | func (r *mutationResolver) UpdateUser(ctx context.Context, input ent.UpdateUserInput) (*ent.User, error) { 32 | u, err := r.controller.User.Update(ctx, input) 33 | if err != nil { 34 | return nil, handler.HandleError(ctx, err) 35 | } 36 | return u, nil 37 | } 38 | 39 | func (r *queryResolver) User(ctx context.Context, id *ulid.ID) (*ent.User, error) { 40 | u, err := r.controller.User.Get(ctx, id) 41 | if err != nil { 42 | return nil, handler.HandleError(ctx, err) 43 | } 44 | return u, nil 45 | } 46 | 47 | func (r *queryResolver) Users(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, where *ent.UserWhereInput) (*ent.UserConnection, error) { 48 | us, err := r.controller.User.List(ctx, after, first, before, last, where) 49 | if err != nil { 50 | return nil, handler.HandleError(ctx, err) 51 | } 52 | return us, nil 53 | } 54 | 55 | func (r *userResolver) CreatedAt(ctx context.Context, obj *ent.User) (string, error) { 56 | return datetime.FormatDate(obj.CreatedAt), nil 57 | } 58 | 59 | func (r *userResolver) UpdatedAt(ctx context.Context, obj *ent.User) (string, error) { 60 | return datetime.FormatDate(obj.UpdatedAt), nil 61 | } 62 | 63 | // User returns generated.UserResolver implementation. 64 | func (r *Resolver) User() generated.UserResolver { return &userResolver{r} } 65 | 66 | type userResolver struct{ *Resolver } 67 | -------------------------------------------------------------------------------- /pkg/const/globalid/globalid.go: -------------------------------------------------------------------------------- 1 | package globalid 2 | 3 | import ( 4 | "fmt" 5 | "golang-clean-architecture-ent-gqlgen/ent/todo" 6 | "golang-clean-architecture-ent-gqlgen/ent/user" 7 | "log" 8 | "reflect" 9 | ) 10 | 11 | type field struct { 12 | Prefix string 13 | Table string 14 | } 15 | 16 | // GlobalIDs maps unique string to tables names. 17 | type GlobalIDs struct { 18 | User field 19 | Todo field 20 | } 21 | 22 | // New generates a map object that is intended to be used as global identification for node interface query. 23 | // Prefix should maintain constrained to 3 characters for encoding the entity type. 24 | func New() GlobalIDs { 25 | return GlobalIDs{ 26 | User: field{ 27 | Prefix: "0AA", 28 | Table: user.Table, 29 | }, 30 | Todo: field{ 31 | Prefix: "0AB", 32 | Table: todo.Table, 33 | }, 34 | } 35 | } 36 | 37 | var globalIDS = New() 38 | var maps = structToMap(&globalIDS) 39 | 40 | // FindTableByID returns table name by passed id. 41 | func (GlobalIDs) FindTableByID(id string) (string, error) { 42 | v, ok := maps[id] 43 | if !ok { 44 | return "", fmt.Errorf("could not map '%s' to a table name", id) 45 | } 46 | return v, nil 47 | } 48 | 49 | func structToMap(data *GlobalIDs) map[string]string { 50 | elem := reflect.ValueOf(data).Elem() 51 | size := elem.NumField() 52 | result := make(map[string]string, size) 53 | 54 | for i := 0; i < size; i++ { 55 | value := elem.Field(i).Interface() 56 | f, ok := value.(field) 57 | if !ok { 58 | log.Fatalf("Cannot convert struct to map") 59 | } 60 | result[f.Prefix] = f.Table 61 | } 62 | 63 | return result 64 | } 65 | -------------------------------------------------------------------------------- /pkg/entity/model/error.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "golang-clean-architecture-ent-gqlgen/pkg/util/environment" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | const ( 11 | // DBError is error code of database 12 | DBError = "DB_ERROR" 13 | // GraphQLError is error code of graphql 14 | GraphQLError = "GRAPHQL_ERROR" 15 | // NotFoundError is error code of not found 16 | NotFoundError = "NOT_FOUND_ERROR" 17 | // ValidationError is error code of validation 18 | ValidationError = "VALIDATION_ERROR" 19 | // BadRequestError is error code of request 20 | BadRequestError = "BAD_REQUEST_ERROR" 21 | // InternalServerError is error code of server error 22 | InternalServerError = "INTERNAL_SERVER_ERROR" 23 | ) 24 | 25 | // StackTrace is used to check to see if the error has already been wrapped by errors.WithStack 26 | type StackTrace interface { 27 | StackTrace() errors.StackTrace 28 | } 29 | 30 | // Error is the standard error type 31 | type Error interface { 32 | Error() string 33 | Code() string 34 | Extensions() map[string]interface{} 35 | Unwrap() error 36 | } 37 | 38 | // NewDBError returns error message related database 39 | func NewDBError(e error) error { 40 | return newError( 41 | DBError, 42 | fmt.Sprintf("%s", e.Error()), 43 | map[string]interface{}{ 44 | "code": DBError, 45 | }, 46 | e, 47 | ) 48 | } 49 | 50 | // NewGraphQLError returns error message related graphql 51 | func NewGraphQLError(e error) error { 52 | return newError( 53 | GraphQLError, 54 | fmt.Sprintf("%s", e.Error()), 55 | map[string]interface{}{ 56 | "code": GraphQLError, 57 | }, 58 | e, 59 | ) 60 | } 61 | 62 | // NewNotFoundError returns error message related not found 63 | func NewNotFoundError(e error, value interface{}) error { 64 | return newError( 65 | NotFoundError, 66 | fmt.Sprintf("%s", e.Error()), 67 | map[string]interface{}{ 68 | "code": NotFoundError, 69 | "value": value, 70 | }, 71 | e, 72 | ) 73 | } 74 | 75 | // NewInvalidParamError returns error message related param 76 | func NewInvalidParamError(e error, value interface{}) error { 77 | return newError( 78 | BadRequestError, 79 | fmt.Sprintf("%s", e.Error()), 80 | map[string]interface{}{ 81 | "code": BadRequestError, 82 | "value": value, 83 | }, 84 | e, 85 | ) 86 | } 87 | 88 | // NewValidationError returns error message related validation 89 | func NewValidationError(e error) error { 90 | return newError( 91 | ValidationError, 92 | fmt.Sprintf("%s", e.Error()), 93 | map[string]interface{}{ 94 | "code": ValidationError, 95 | }, 96 | e, 97 | ) 98 | } 99 | 100 | // NewInternalServerError returns error message related syntax or other issues 101 | func NewInternalServerError(e error) error { 102 | return newError( 103 | InternalServerError, 104 | fmt.Sprintf("%s", e.Error()), 105 | map[string]interface{}{ 106 | "code": InternalServerError, 107 | }, 108 | e, 109 | ) 110 | } 111 | 112 | type err struct { 113 | err error 114 | code string 115 | message string 116 | extensions map[string]interface{} 117 | } 118 | 119 | func (e *err) Error() string { return e.message } 120 | func (e *err) Code() string { return e.code } 121 | func (e *err) Extensions() map[string]interface{} { return e.extensions } 122 | func (e *err) Unwrap() error { return e.err } 123 | 124 | // IsStackTrace checks to see if the error with stack strace 125 | func IsStackTrace(e error) bool { 126 | _, ok := e.(StackTrace) 127 | return ok 128 | } 129 | 130 | // IsError checks to see if the error is generated by model 131 | func IsError(e error) bool { 132 | _, ok := e.(Error) 133 | return ok 134 | } 135 | 136 | func newError(code string, message string, extensions map[string]interface{}, e error) error { 137 | newErr := &err{ 138 | err: e, 139 | code: code, 140 | message: message, 141 | extensions: extensions, 142 | } 143 | if IsStackTrace(e) { 144 | return newErr 145 | } 146 | 147 | return withStackTrace(newErr) 148 | } 149 | 150 | func withStackTrace(e error) error { 151 | ews := errors.WithStack(e) 152 | 153 | if environment.IsDev() { 154 | fmt.Printf("%+v\n", ews) 155 | } 156 | 157 | return ews 158 | } 159 | -------------------------------------------------------------------------------- /pkg/entity/model/id.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent/schema/ulid" 5 | ) 6 | 7 | // ID implements a ULID 8 | type ID = ulid.ID 9 | -------------------------------------------------------------------------------- /pkg/entity/model/node.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "golang-clean-architecture-ent-gqlgen/ent" 4 | 5 | // Node wraps the basic Node method. 6 | type Node = ent.Noder 7 | -------------------------------------------------------------------------------- /pkg/entity/model/pagination.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "golang-clean-architecture-ent-gqlgen/ent" 4 | 5 | // Cursor of an edge type. 6 | type Cursor = ent.Cursor 7 | 8 | // PageInfo of a connection type. 9 | type PageInfo = ent.PageInfo 10 | -------------------------------------------------------------------------------- /pkg/entity/model/todo.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "golang-clean-architecture-ent-gqlgen/ent" 4 | 5 | // Todo is the model entity for the Todo schema. 6 | type Todo = ent.Todo 7 | 8 | // CreateTodoInput represents a mutation input for creating todos. 9 | type CreateTodoInput = ent.CreateTodoInput 10 | 11 | // UpdateTodoInput represents a mutation input for updating todos. 12 | type UpdateTodoInput = ent.UpdateTodoInput 13 | -------------------------------------------------------------------------------- /pkg/entity/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "golang-clean-architecture-ent-gqlgen/ent" 4 | 5 | // User is the model entity for the User schema. 6 | type User = ent.User 7 | 8 | // UserConnection is the connection containing edges to User. 9 | type UserConnection = ent.UserConnection 10 | 11 | // UserWhereInput represents a where input for filtering User queries. 12 | type UserWhereInput = ent.UserWhereInput 13 | 14 | // CreateUserInput represents a mutation input for creating users. 15 | type CreateUserInput = ent.CreateUserInput 16 | 17 | // UpdateUserInput represents a mutation input for updating users. 18 | type UpdateUserInput = ent.UpdateUserInput 19 | -------------------------------------------------------------------------------- /pkg/infrastructure/datastore/datastore.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/config" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | 7 | "entgo.io/ent/dialect" 8 | 9 | "github.com/go-sql-driver/mysql" 10 | ) 11 | 12 | // New returns data source name 13 | func New() string { 14 | mc := mysql.Config{ 15 | User: config.C.Database.User, 16 | Passwd: config.C.Database.Password, 17 | Net: config.C.Database.Net, 18 | Addr: config.C.Database.Addr, 19 | DBName: config.C.Database.DBName, 20 | AllowNativePasswords: config.C.Database.AllowNativePasswords, 21 | Params: map[string]string{ 22 | "parseTime": config.C.Database.Params.ParseTime, 23 | "charset": config.C.Database.Params.Charset, 24 | "loc": config.C.Database.Params.Loc, 25 | }, 26 | } 27 | 28 | return mc.FormatDSN() 29 | } 30 | 31 | // NewClient returns an orm client 32 | func NewClient() (*ent.Client, error) { 33 | var entOptions []ent.Option 34 | entOptions = append(entOptions, ent.Debug()) 35 | 36 | d := New() 37 | 38 | return ent.Open(dialect.MySQL, d, entOptions...) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/infrastructure/graphql/graphql.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent" 5 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 6 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/resolver" 7 | 8 | "entgo.io/contrib/entgql" 9 | 10 | "github.com/99designs/gqlgen/graphql/handler" 11 | ) 12 | 13 | // NewServer generates graphql server 14 | func NewServer(client *ent.Client, controller controller.Controller) *handler.Server { 15 | srv := handler.NewDefaultServer(resolver.NewSchema(client, controller)) 16 | srv.Use(entgql.Transactioner{TxOpener: client}) 17 | 18 | return srv 19 | } 20 | -------------------------------------------------------------------------------- /pkg/infrastructure/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/99designs/gqlgen/graphql/handler" 7 | "github.com/99designs/gqlgen/graphql/playground" 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | // Path of route 13 | const ( 14 | QueryPath = "/query" 15 | PlaygroundPath = "/playground" 16 | ) 17 | 18 | // New creates route endpoint 19 | func New(srv *handler.Server) *echo.Echo { 20 | e := echo.New() 21 | e.Use(middleware.Recover()) 22 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 23 | AllowOrigins: []string{"*"}, 24 | AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodOptions}, 25 | AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderXRequestedWith, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, 26 | })) 27 | 28 | { 29 | e.POST(QueryPath, echo.WrapHandler(srv)) 30 | e.GET(PlaygroundPath, func(c echo.Context) error { 31 | playground.Handler("GraphQL", QueryPath).ServeHTTP(c.Response(), c.Request()) 32 | return nil 33 | }) 34 | } 35 | 36 | return e 37 | } 38 | -------------------------------------------------------------------------------- /pkg/registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent" 5 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 6 | ) 7 | 8 | type registry struct { 9 | client *ent.Client 10 | } 11 | 12 | // Registry is an interface of registry 13 | type Registry interface { 14 | NewController() controller.Controller 15 | } 16 | 17 | // New registers entire controller with dependencies 18 | func New(client *ent.Client) Registry { 19 | return ®istry{ 20 | client: client, 21 | } 22 | } 23 | 24 | // NewController generates controllers 25 | func (r *registry) NewController() controller.Controller { 26 | return controller.Controller{ 27 | User: r.NewUserController(), 28 | Todo: r.NewTodoController(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/registry/todo.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 5 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/repository" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/usecase" 7 | ) 8 | 9 | func (r *registry) NewTodoController() controller.Todo { 10 | repo := repository.NewTodoRepository(r.client) 11 | u := usecase.NewTodoUsecase(repo) 12 | 13 | return controller.NewTodoController(u) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/registry/user.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 5 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/repository" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/usecase" 7 | ) 8 | 9 | // NewUserController conforms to interface 10 | func (r *registry) NewUserController() controller.User { 11 | repo := repository.NewUserRepository(r.client) 12 | u := usecase.NewUserUsecase(repo) 13 | 14 | return controller.NewUserController(u) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/usecase/repository/todo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | ) 7 | 8 | // Todo is interface of repository 9 | type Todo interface { 10 | Get(ctx context.Context, id *model.ID) (*model.Todo, error) 11 | List(ctx context.Context) ([]*model.Todo, error) 12 | Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) 13 | Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/usecase/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | ) 7 | 8 | // User is interface of repository 9 | type User interface { 10 | Get(ctx context.Context, id *model.ID) (*model.User, error) 11 | List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) 12 | Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) 13 | CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) 14 | Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/usecase/usecase/todo.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/repository" 7 | ) 8 | 9 | type testTodo struct { 10 | testTodoRepository repository.Todo 11 | } 12 | 13 | // Todo is an interface of test user 14 | type Todo interface { 15 | Get(ctx context.Context, id *model.ID) (*model.Todo, error) 16 | List(ctx context.Context) ([]*model.Todo, error) 17 | Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) 18 | Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) 19 | } 20 | 21 | // NewTodoUsecase generates test user repository 22 | func NewTodoUsecase(r repository.Todo) Todo { 23 | return &testTodo{testTodoRepository: r} 24 | } 25 | 26 | func (t *testTodo) Get(ctx context.Context, id *model.ID) (*model.Todo, error) { 27 | return t.testTodoRepository.Get(ctx, id) 28 | } 29 | 30 | func (t *testTodo) List(ctx context.Context) ([]*model.Todo, error) { 31 | return t.testTodoRepository.List(ctx) 32 | } 33 | 34 | func (t *testTodo) Create(ctx context.Context, input model.CreateTodoInput) (*model.Todo, error) { 35 | return t.testTodoRepository.Create(ctx, input) 36 | } 37 | 38 | func (t *testTodo) Update(ctx context.Context, input model.UpdateTodoInput) (*model.Todo, error) { 39 | return t.testTodoRepository.Update(ctx, input) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/usecase/usecase/user.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/pkg/entity/model" 6 | "golang-clean-architecture-ent-gqlgen/pkg/usecase/repository" 7 | ) 8 | 9 | type user struct { 10 | userRepository repository.User 11 | } 12 | 13 | // User of usecase. 14 | type User interface { 15 | Get(ctx context.Context, id *model.ID) (*model.User, error) 16 | List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) 17 | Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) 18 | CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) 19 | Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) 20 | } 21 | 22 | // NewUserUsecase returns user usecase. 23 | func NewUserUsecase(r repository.User) User { 24 | return &user{userRepository: r} 25 | } 26 | 27 | func (u *user) Get(ctx context.Context, id *model.ID) (*model.User, error) { 28 | return u.userRepository.Get(ctx, id) 29 | } 30 | 31 | func (u *user) List(ctx context.Context, after *model.Cursor, first *int, before *model.Cursor, last *int, where *model.UserWhereInput) (*model.UserConnection, error) { 32 | return u.userRepository.List(ctx, after, first, before, last, where) 33 | } 34 | 35 | func (u *user) Create(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 36 | return u.userRepository.Create(ctx, input) 37 | } 38 | 39 | func (u *user) CreateWithTodo(ctx context.Context, input model.CreateUserInput) (*model.User, error) { 40 | return u.userRepository.CreateWithTodo(ctx, input) 41 | } 42 | 43 | func (u *user) Update(ctx context.Context, input model.UpdateUserInput) (*model.User, error) { 44 | return u.userRepository.Update(ctx, input) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/util/datetime/datetime.go: -------------------------------------------------------------------------------- 1 | package datetime 2 | 3 | import "time" 4 | 5 | // FormatDate returns time formatted as RFC3339 (2021-11-10T17:41:48+09:00) 6 | func FormatDate(t time.Time) string { 7 | return t.Format(time.RFC3339) 8 | } 9 | -------------------------------------------------------------------------------- /pkg/util/environment/environment.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import "os" 4 | 5 | // Application Environment name 6 | const ( 7 | Development = "development" 8 | Test = "test" 9 | E2E = "e2e" 10 | ) 11 | 12 | // IsDev returns APP_ENV in development mode 13 | func IsDev() bool { 14 | println("os.Getenv(APP_ENV): ", os.Getenv("APP_ENV")) 15 | return os.Getenv("APP_ENV") == Development 16 | } 17 | 18 | // IsTest returns APP_ENV in test mode 19 | func IsTest() bool { 20 | return os.Getenv("APP_ENV") == Test 21 | } 22 | 23 | // IsE2E returns APP_ENV in e2e mode 24 | func IsE2E() bool { 25 | return os.Getenv("APP_ENV") == E2E 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/mutation/user_test.go: -------------------------------------------------------------------------------- 1 | package mutation_test 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/router" 7 | "golang-clean-architecture-ent-gqlgen/testutil" 8 | "golang-clean-architecture-ent-gqlgen/testutil/e2e" 9 | "net/http" 10 | "testing" 11 | 12 | "github.com/gavv/httpexpect/v2" 13 | ) 14 | 15 | func TestUser_CreateUser(t *testing.T) { 16 | expect, client, teardown := e2e.Setup(t, e2e.SetupOption{ 17 | Teardown: func(t *testing.T, client *ent.Client) { 18 | testutil.DropUser(t, client) 19 | }, 20 | }) 21 | defer teardown() 22 | 23 | tests := []struct { 24 | name string 25 | arrange func(t *testing.T) 26 | act func(t *testing.T) *httpexpect.Response 27 | assert func(t *testing.T, got *httpexpect.Response) 28 | args struct { 29 | ctx context.Context 30 | } 31 | teardown func(t *testing.T) 32 | }{ 33 | { 34 | name: "It should create test user", 35 | arrange: func(t *testing.T) {}, 36 | act: func(t *testing.T) *httpexpect.Response { 37 | return expect.POST(router.QueryPath).WithJSON(map[string]string{ 38 | "query": ` 39 | mutation { 40 | createUser(input: {name: "Tom1", age: 20}) { 41 | age 42 | name 43 | id 44 | createdAt 45 | updatedAt 46 | } 47 | }`, 48 | }).Expect() 49 | }, 50 | assert: func(t *testing.T, got *httpexpect.Response) { 51 | got.Status(http.StatusOK) 52 | data := e2e.GetData(got).Object() 53 | testUser := e2e.GetObject(data, "createUser") 54 | testUser.Value("age").Number().Equal(20) 55 | testUser.Value("name").String().Equal("Tom1") 56 | }, 57 | teardown: func(t *testing.T) { 58 | testutil.DropUser(t, client) 59 | }, 60 | }, 61 | { 62 | name: "It should NOT create test user when the length of the name is over", 63 | arrange: func(t *testing.T) {}, 64 | act: func(t *testing.T) *httpexpect.Response { 65 | return expect.POST(router.QueryPath).WithJSON(map[string]string{ 66 | "query": ` 67 | mutation { 68 | createUser(input: {name: "Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1Tom1", age: 20}) { 69 | age 70 | name 71 | id 72 | createdAt 73 | updatedAt 74 | } 75 | }`, 76 | }).Expect() 77 | }, 78 | assert: func(t *testing.T, got *httpexpect.Response) { 79 | got.Status(http.StatusOK) 80 | data := e2e.GetData(got) 81 | data.Null() 82 | 83 | errors := e2e.GetErrors(got) 84 | errors.Array().Length().Equal(1) 85 | }, 86 | teardown: func(t *testing.T) { 87 | testutil.DropUser(t, client) 88 | }, 89 | }, 90 | } 91 | 92 | for _, tt := range tests { 93 | t.Run(tt.name, func(t *testing.T) { 94 | tt.arrange(t) 95 | got := tt.act(t) 96 | tt.assert(t, got) 97 | tt.teardown(t) 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /testutil/config.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/config" 5 | "golang-clean-architecture-ent-gqlgen/pkg/util/environment" 6 | ) 7 | 8 | // ReadConfig reads config file for test. 9 | func ReadConfig() { 10 | config.ReadConfig(config.ReadConfigOption{ 11 | AppEnv: environment.Test, 12 | }) 13 | } 14 | 15 | // ReadConfigE2E reads config file for e2e. 16 | func ReadConfigE2E() { 17 | config.ReadConfig(config.ReadConfigOption{ 18 | AppEnv: environment.E2E, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /testutil/database.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "context" 5 | "golang-clean-architecture-ent-gqlgen/ent" 6 | "golang-clean-architecture-ent-gqlgen/ent/enttest" 7 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/datastore" 8 | "testing" 9 | 10 | "entgo.io/ent/dialect" 11 | ) 12 | 13 | // NewDBClient loads database for test. 14 | func NewDBClient(t *testing.T) *ent.Client { 15 | d := datastore.New() 16 | return enttest.Open(t, dialect.MySQL, d) 17 | } 18 | 19 | // DropAll drops all data from database. 20 | func DropAll(t *testing.T, client *ent.Client) { 21 | t.Log("drop data from database") 22 | DropUser(t, client) 23 | DropTodo(t, client) 24 | } 25 | 26 | // DropUser drops data from users. 27 | func DropUser(t *testing.T, client *ent.Client) { 28 | ctx := context.Background() 29 | _, err := client.User.Delete().Exec(ctx) 30 | 31 | if err != nil { 32 | t.Error(err) 33 | t.FailNow() 34 | } 35 | } 36 | 37 | // DropTodo drops data from todos. 38 | func DropTodo(t *testing.T, client *ent.Client) { 39 | ctx := context.Background() 40 | _, err := client.Todo.Delete().Exec(ctx) 41 | 42 | if err != nil { 43 | t.Error(err) 44 | t.FailNow() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /testutil/e2e/e2e.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "golang-clean-architecture-ent-gqlgen/ent" 5 | "golang-clean-architecture-ent-gqlgen/pkg/adapter/controller" 6 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/graphql" 7 | "golang-clean-architecture-ent-gqlgen/pkg/infrastructure/router" 8 | "golang-clean-architecture-ent-gqlgen/pkg/registry" 9 | "golang-clean-architecture-ent-gqlgen/testutil" 10 | "net/http/httptest" 11 | "testing" 12 | 13 | "github.com/gavv/httpexpect/v2" 14 | ) 15 | 16 | // SetupOption is an option of SetupE2E 17 | type SetupOption struct { 18 | Teardown func(t *testing.T, client *ent.Client) 19 | } 20 | 21 | // Setup set up database and server for E2E test 22 | func Setup(t *testing.T, option SetupOption) (expect *httpexpect.Expect, client *ent.Client, teardown func()) { 23 | t.Helper() 24 | testutil.ReadConfigE2E() 25 | 26 | client = testutil.NewDBClient(t) 27 | ctrl := newController(client) 28 | gqlsrv := graphql.NewServer(client, ctrl) 29 | e := router.New(gqlsrv) 30 | 31 | srv := httptest.NewServer(e) 32 | 33 | return httpexpect.WithConfig(httpexpect.Config{ 34 | BaseURL: srv.URL, 35 | Reporter: httpexpect.NewAssertReporter(t), 36 | Printers: []httpexpect.Printer{ 37 | httpexpect.NewDebugPrinter(t, true), 38 | }, 39 | }), client, func() { 40 | option.Teardown(t, client) 41 | defer client.Close() 42 | defer srv.Close() 43 | } 44 | } 45 | 46 | func newController(client *ent.Client) controller.Controller { 47 | r := registry.New(client) 48 | return r.NewController() 49 | } 50 | 51 | // GetData gets data from graphql response. 52 | func GetData(e *httpexpect.Response) *httpexpect.Value { 53 | return e.JSON().Path("$.data") 54 | } 55 | 56 | // GetObject return data from path. 57 | // Path returns a new Value object for child object(s) matching given 58 | // JSONPath expression. 59 | // Example 1: 60 | // json := `{"users": [{"name": "john"}, {"name": "bob"}]}` 61 | // value := NewValue(t, json) 62 | // 63 | // value.Path("$.users[0].name").String().Equal("john") 64 | // value.Path("$.users[1].name").String().Equal("bob") 65 | func GetObject(obj *httpexpect.Object, path string) *httpexpect.Object { 66 | return obj.Path("$." + path).Object() 67 | } 68 | 69 | // GetErrors return errors from graphql response. 70 | func GetErrors(e *httpexpect.Response) *httpexpect.Value { 71 | return e.JSON().Path("$.errors") 72 | } 73 | --------------------------------------------------------------------------------