GraphQL Framework
This commit is contained in:
		
							parent
							
								
									ecf981495e
								
							
						
					
					
						commit
						7e6454c593
					
				
							
								
								
									
										32
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								cmd/main.go
									
									
									
									
									
								
							| @ -7,6 +7,10 @@ import ( | |||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/cmd/server" |     "reichard.io/imagini/cmd/server" | ||||||
|  | 
 | ||||||
|  |     "reichard.io/imagini/plugin" | ||||||
|  |     "github.com/99designs/gqlgen/api" | ||||||
|  | 	"github.com/99designs/gqlgen/codegen/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type UTCFormatter struct { | type UTCFormatter struct { | ||||||
| @ -21,7 +25,6 @@ func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) { | |||||||
| func main() { | func main() { | ||||||
|     log.SetFormatter(UTCFormatter{&log.TextFormatter{FullTimestamp: true}}) |     log.SetFormatter(UTCFormatter{&log.TextFormatter{FullTimestamp: true}}) | ||||||
| 
 | 
 | ||||||
|     log.Info("Starting Imagini") |  | ||||||
|     app := &cli.App{ |     app := &cli.App{ | ||||||
|         Name: "Imagini", |         Name: "Imagini", | ||||||
|         Usage: "A self hosted photo library.", |         Usage: "A self hosted photo library.", | ||||||
| @ -32,6 +35,11 @@ func main() { | |||||||
|                 Usage:   "Start Imagini web server.", |                 Usage:   "Start Imagini web server.", | ||||||
|                 Action:  cmdServer, |                 Action:  cmdServer, | ||||||
|             }, |             }, | ||||||
|  |             { | ||||||
|  | 				Name:  "generate", | ||||||
|  | 				Usage: "generate graphql schema", | ||||||
|  | 				Action: cmdGenerate, | ||||||
|  | 			}, | ||||||
|         }, |         }, | ||||||
|     } |     } | ||||||
|     err := app.Run(os.Args) |     err := app.Run(os.Args) | ||||||
| @ -41,6 +49,7 @@ func main() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func cmdServer(ctx *cli.Context) error { | func cmdServer(ctx *cli.Context) error { | ||||||
|  |     log.Info("Starting Imagini Server") | ||||||
|     server := server.NewServer() |     server := server.NewServer() | ||||||
|     server.StartServer() |     server.StartServer() | ||||||
| 
 | 
 | ||||||
| @ -53,3 +62,24 @@ func cmdServer(ctx *cli.Context) error { | |||||||
| 
 | 
 | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func cmdGenerate(ctx *cli.Context) error { | ||||||
|  |     log.Info("Generating Imagini Models") | ||||||
|  |     gqlgenConf, err := config.LoadConfigFromDefaultLocations() | ||||||
|  |     if err != nil { | ||||||
|  |         log.Panic("Failed to load config", err.Error()) | ||||||
|  |         os.Exit(2) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     log.Info("Generating Schema...") | ||||||
|  |     err = api.Generate(gqlgenConf, | ||||||
|  |         api.AddPlugin(plugin.New()), | ||||||
|  |     ) | ||||||
|  |     log.Info("Schema Generation Done") | ||||||
|  |     if err != nil { | ||||||
|  |         log.Panic(err.Error()) | ||||||
|  |         os.Exit(3) | ||||||
|  |     } | ||||||
|  |     os.Exit(0) | ||||||
|  |     return nil | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,6 +3,7 @@ module reichard.io/imagini | |||||||
| go 1.15 | go 1.15 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	github.com/99designs/gqlgen v0.13.0 | ||||||
| 	github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect | 	github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| 	github.com/disintegration/imaging v1.6.2 // indirect | 	github.com/disintegration/imaging v1.6.2 // indirect | ||||||
| @ -14,6 +15,7 @@ require ( | |||||||
| 	github.com/sirupsen/logrus v1.7.0 | 	github.com/sirupsen/logrus v1.7.0 | ||||||
| 	github.com/tus/tusd v1.4.0 | 	github.com/tus/tusd v1.4.0 | ||||||
| 	github.com/urfave/cli/v2 v2.3.0 | 	github.com/urfave/cli/v2 v2.3.0 | ||||||
|  | 	github.com/vektah/gqlparser/v2 v2.1.0 | ||||||
| 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad | 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad | ||||||
| 	golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect | 	golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect | ||||||
| 	gorm.io/driver/sqlite v1.1.4 | 	gorm.io/driver/sqlite v1.1.4 | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								go.sum
									
									
									
									
									
								
							| @ -2,9 +2,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT | |||||||
| cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||||||
| cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= | ||||||
| cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= | cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= | ||||||
|  | github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA= | ||||||
|  | github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= | ||||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||||
|  | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= | ||||||
|  | github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= | ||||||
|  | github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= | ||||||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||||||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||||||
|  | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= | ||||||
|  | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= | ||||||
| github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= | github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= | ||||||
| github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | ||||||
| github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | ||||||
| @ -22,6 +29,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs | |||||||
| github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= | github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= | ||||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||||
|  | github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= | ||||||
| github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= | ||||||
| github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= | ||||||
| github.com/dsoprea/go-exif v0.0.0-20201216222538-db167117f483 h1:zJb7OUzMMSul61UUhYXWNOXc9nO1lexj3jsAgoDtCqg= | github.com/dsoprea/go-exif v0.0.0-20201216222538-db167117f483 h1:zJb7OUzMMSul61UUhYXWNOXc9nO1lexj3jsAgoDtCqg= | ||||||
| @ -41,6 +49,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m | |||||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||||
| github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= | github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= | ||||||
| github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= | github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= | ||||||
|  | github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | ||||||
| github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= | ||||||
| github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||||
| github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= | github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= | ||||||
| @ -52,6 +61,7 @@ github.com/go-macaron/gzip v0.0.0-20200329073552-98214d7a897e/go.mod h1:1if9hBU2 | |||||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= | github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= | ||||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= | github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= | ||||||
| github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||||||
|  | github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||||
| github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||||
| github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | ||||||
| github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | ||||||
| @ -77,9 +87,14 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ | |||||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
| github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||||
|  | github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | ||||||
|  | github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | ||||||
|  | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= | ||||||
|  | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
| github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= | ||||||
| github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= | ||||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
|  | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= | ||||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
| github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= | ||||||
| github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||||
| @ -98,6 +113,9 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT | |||||||
| github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= | ||||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||||
| github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | ||||||
|  | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||||
|  | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
|  | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw= | github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw= | ||||||
| github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= | github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= | ||||||
| github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/Kb2ovtuYgcRFjNmf9ntcn8BPIbpRM4k= | github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/Kb2ovtuYgcRFjNmf9ntcn8BPIbpRM4k= | ||||||
| @ -109,14 +127,24 @@ github.com/lestrrat-go/jwx v1.0.8/go.mod h1:6XJ5sxHF5U116AxYxeHfTnfsZRMgmeKY214z | |||||||
| github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35 h1:lea8Wt+1ePkVrI2/WD+NgQT5r/XsLAzxeqtyFLcEs10= | github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35 h1:lea8Wt+1ePkVrI2/WD+NgQT5r/XsLAzxeqtyFLcEs10= | ||||||
| github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= | github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= | ||||||
| github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= | github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= | ||||||
|  | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= | ||||||
|  | github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= | ||||||
|  | github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= | ||||||
|  | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||||
|  | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||||
|  | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||||
| github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= | github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= | ||||||
| github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= | ||||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||||
|  | github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8= | ||||||
|  | github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
| github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | ||||||
| github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||||
| github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= | github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= | ||||||
|  | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= | ||||||
|  | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= | ||||||
| github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= | ||||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
| @ -131,11 +159,15 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: | |||||||
| github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||||
| github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||||
| github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||||
|  | github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= | ||||||
| github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= | ||||||
| github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
|  | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||||
| github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= | github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= | ||||||
|  | github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= | ||||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= | ||||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||||||
|  | github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= | ||||||
| github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||||||
| github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||||
| github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= | ||||||
| @ -148,6 +180,7 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:s | |||||||
| github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
| @ -159,8 +192,13 @@ github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6x | |||||||
| github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | ||||||
| github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= | ||||||
| github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||||||
|  | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= | ||||||
| github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | ||||||
| github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | ||||||
|  | github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= | ||||||
|  | github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= | ||||||
|  | github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= | ||||||
|  | github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= | ||||||
| github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs= | github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs= | ||||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||||
| @ -186,6 +224,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx | |||||||
| golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||||
| golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||||
|  | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||||
|  | golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= | ||||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| @ -215,11 +255,13 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h | |||||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= | ||||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= | ||||||
| golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||||
| @ -230,17 +272,22 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb | |||||||
| golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | ||||||
| golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||||
| golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||||
| golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||||
| golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||||
| golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||||
|  | golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||||
| golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||||
|  | golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU= | ||||||
| golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||||
| google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= | google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= | ||||||
| @ -261,6 +308,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa | |||||||
| gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxlpi0JI4Umw= | gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxlpi0JI4Umw= | ||||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= | gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= | ||||||
| gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= | gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= | ||||||
| gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||||
| @ -270,6 +318,7 @@ gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4 | |||||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | ||||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| @ -284,3 +333,5 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh | |||||||
| honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||||
|  | sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= | ||||||
|  | sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								gqlgen.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								gqlgen.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | # Where are all the schema files located? globs are supported eg  src/**/*.graphqls | ||||||
|  | schema: | ||||||
|  |   - graph/*.graphqls | ||||||
|  | 
 | ||||||
|  | # Where should the generated server code go? | ||||||
|  | exec: | ||||||
|  |   filename: graph/generated/generated.go | ||||||
|  |   package: generated | ||||||
|  | 
 | ||||||
|  | # Uncomment to enable federation | ||||||
|  | # federation: | ||||||
|  | #   filename: graph/generated/federation.go | ||||||
|  | #   package: generated | ||||||
|  | 
 | ||||||
|  | # Where should any generated models go? | ||||||
|  | model: | ||||||
|  |   filename: graph/model/models_gen.go | ||||||
|  |   package: model | ||||||
|  | 
 | ||||||
|  | # Where should the resolver implementations go? | ||||||
|  | resolver: | ||||||
|  |   layout: follow-schema | ||||||
|  |   dir: graph | ||||||
|  |   package: graph | ||||||
|  | 
 | ||||||
|  | # Optional: turn on use `gqlgen:"fieldName"` tags in your models | ||||||
|  | # struct_tag: json | ||||||
|  | 
 | ||||||
|  | # Optional: turn on to use []Thing instead of []*Thing | ||||||
|  | # omit_slice_element_pointers: false | ||||||
|  | 
 | ||||||
|  | # Optional: set to speed up generation time by not performing a final validation pass. | ||||||
|  | # skip_validation: true | ||||||
|  | 
 | ||||||
|  | # gqlgen will search for any type names in the schema in these go packages | ||||||
|  | # if they match it will use them, otherwise it will generate them. | ||||||
|  | autobind: | ||||||
|  |   - "reichard.io/imagini/graph/model" | ||||||
|  | 
 | ||||||
|  | # This section declares type mapping between the GraphQL and go type systems | ||||||
|  | # | ||||||
|  | # The first line in each type will be used as defaults for resolver arguments and | ||||||
|  | # modelgen, the others will be allowed when binding to fields. Configure them to | ||||||
|  | # your liking | ||||||
|  | models: | ||||||
|  |   ID: | ||||||
|  |     model: | ||||||
|  |       - github.com/99designs/gqlgen/graphql.ID | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int64 | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int32 | ||||||
|  |   Int: | ||||||
|  |     model: | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int64 | ||||||
|  |       - github.com/99designs/gqlgen/graphql.Int32 | ||||||
							
								
								
									
										9341
									
								
								graph/generated/generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9341
									
								
								graph/generated/generated.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										397
									
								
								graph/model/models_gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								graph/model/models_gen.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,397 @@ | |||||||
|  | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. | ||||||
|  | 
 | ||||||
|  | package model | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/99designs/gqlgen/graphql" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Album struct { | ||||||
|  | 	ID        *string    `json:"id" gorm:"primarykey;not null"` | ||||||
|  | 	CreatedAt *time.Time `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *time.Time `json:"updatedAt" ` | ||||||
|  | 	Name      string     `json:"name" gorm:"unique;not null"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AlbumFilter struct { | ||||||
|  | 	ID        *IDFilter        `json:"id" ` | ||||||
|  | 	CreatedAt *TimeFilter      `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *TimeFilter      `json:"updatedAt" ` | ||||||
|  | 	Name      *StringFilter    `json:"name" ` | ||||||
|  | 	And       *MediaItemFilter `json:"and" ` | ||||||
|  | 	Or        *MediaItemFilter `json:"or" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AlbumResponse struct { | ||||||
|  | 	Data     []*Album  `json:"data" ` | ||||||
|  | 	PageInfo *PageInfo `json:"pageInfo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AuthTypeFilter struct { | ||||||
|  | 	EqualTo    *AuthType  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo *AuthType  `json:"notEqualTo" ` | ||||||
|  | 	In         []AuthType `json:"in" ` | ||||||
|  | 	NotIn      []AuthType `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type BooleanFilter struct { | ||||||
|  | 	EqualTo    *bool `json:"equalTo" ` | ||||||
|  | 	NotEqualTo *bool `json:"notEqualTo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Device struct { | ||||||
|  | 	ID         *string    `json:"id" gorm:"primarykey;not null"` | ||||||
|  | 	CreatedAt  *time.Time `json:"createdAt" ` | ||||||
|  | 	UpdatedAt  *time.Time `json:"updatedAt" ` | ||||||
|  | 	Name       string     `json:"name" gorm:"not null"` | ||||||
|  | 	Type       DeviceType `json:"type" gorm:"default:Unknown;not null"` | ||||||
|  | 	User       *User      `json:"user" gorm:"ForeignKey:ID;not null"` | ||||||
|  | 	RefreshKey *string    `json:"refreshKey" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DeviceFilter struct { | ||||||
|  | 	ID        *IDFilter         `json:"id" ` | ||||||
|  | 	CreatedAt *TimeFilter       `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *TimeFilter       `json:"updatedAt" ` | ||||||
|  | 	Name      *StringFilter     `json:"name" ` | ||||||
|  | 	Type      *DeviceTypeFilter `json:"type" ` | ||||||
|  | 	And       *MediaItemFilter  `json:"and" ` | ||||||
|  | 	Or        *MediaItemFilter  `json:"or" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DeviceResponse struct { | ||||||
|  | 	Data     []*Device `json:"data" ` | ||||||
|  | 	PageInfo *PageInfo `json:"pageInfo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DeviceTypeFilter struct { | ||||||
|  | 	EqualTo    *DeviceType  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo *DeviceType  `json:"notEqualTo" ` | ||||||
|  | 	In         []DeviceType `json:"in" ` | ||||||
|  | 	NotIn      []DeviceType `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type FloatFilter struct { | ||||||
|  | 	EqualTo           *float64  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo        *float64  `json:"notEqualTo" ` | ||||||
|  | 	LessThan          *float64  `json:"lessThan" ` | ||||||
|  | 	LessThanOrEqualTo *float64  `json:"lessThanOrEqualTo" ` | ||||||
|  | 	MoreThan          *float64  `json:"moreThan" ` | ||||||
|  | 	MoreThanOrEqualTo *float64  `json:"moreThanOrEqualTo" ` | ||||||
|  | 	In                []float64 `json:"in" ` | ||||||
|  | 	NotIn             []float64 `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IDFilter struct { | ||||||
|  | 	EqualTo    *string  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo *string  `json:"notEqualTo" ` | ||||||
|  | 	In         []string `json:"in" ` | ||||||
|  | 	NotIn      []string `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IntFilter struct { | ||||||
|  | 	EqualTo           *int  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo        *int  `json:"notEqualTo" ` | ||||||
|  | 	LessThan          *int  `json:"lessThan" ` | ||||||
|  | 	LessThanOrEqualTo *int  `json:"lessThanOrEqualTo" ` | ||||||
|  | 	MoreThan          *int  `json:"moreThan" ` | ||||||
|  | 	MoreThanOrEqualTo *int  `json:"moreThanOrEqualTo" ` | ||||||
|  | 	In                []int `json:"in" ` | ||||||
|  | 	NotIn             []int `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MediaItem struct { | ||||||
|  | 	ID        *string    `json:"id" gorm:"primarykey;not null"` | ||||||
|  | 	CreatedAt *time.Time `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *time.Time `json:"updatedAt" ` | ||||||
|  | 	ExifDate  *time.Time `json:"exifDate" ` | ||||||
|  | 	Latitude  *float64   `json:"latitude" ` | ||||||
|  | 	Longitude *float64   `json:"longitude" ` | ||||||
|  | 	IsVideo   bool       `json:"isVideo" gorm:"default:false;not null"` | ||||||
|  | 	FileName  string     `json:"fileName" gorm:"not null"` | ||||||
|  | 	OrigName  string     `json:"origName" gorm:"not null"` | ||||||
|  | 	Tags      []*Tag     `json:"tags" gorm:"many2many:media_tags"` | ||||||
|  | 	Albums    []*Album   `json:"albums" gorm:"many2many:media_albums"` | ||||||
|  | 	User      *User      `json:"user" gorm:"ForeignKey:ID;not null"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MediaItemFilter struct { | ||||||
|  | 	ID        *IDFilter        `json:"id" ` | ||||||
|  | 	CreatedAt *TimeFilter      `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *TimeFilter      `json:"updatedAt" ` | ||||||
|  | 	ExifDate  *TimeFilter      `json:"exifDate" ` | ||||||
|  | 	Latitude  *FloatFilter     `json:"latitude" ` | ||||||
|  | 	Longitude *FloatFilter     `json:"longitude" ` | ||||||
|  | 	IsVideo   *BooleanFilter   `json:"isVideo" ` | ||||||
|  | 	OrigName  *StringFilter    `json:"origName" ` | ||||||
|  | 	Tags      *TagFilter       `json:"tags" ` | ||||||
|  | 	Albums    *AlbumFilter     `json:"albums" ` | ||||||
|  | 	And       *MediaItemFilter `json:"and" ` | ||||||
|  | 	Or        *MediaItemFilter `json:"or" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MediaItemResponse struct { | ||||||
|  | 	Data     []*MediaItem `json:"data" ` | ||||||
|  | 	PageInfo *PageInfo    `json:"pageInfo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NewAlbum struct { | ||||||
|  | 	Name string `json:"name" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NewDevice struct { | ||||||
|  | 	Name string `json:"name" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NewMediaItem struct { | ||||||
|  | 	File   graphql.Upload `json:"file" ` | ||||||
|  | 	Tags   []string       `json:"tags" ` | ||||||
|  | 	Albums []string       `json:"albums" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NewTag struct { | ||||||
|  | 	Name string `json:"name" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NewUser struct { | ||||||
|  | 	Email     string   `json:"email" ` | ||||||
|  | 	Username  string   `json:"username" ` | ||||||
|  | 	FirstName *string  `json:"firstName" ` | ||||||
|  | 	LastName  *string  `json:"lastName" ` | ||||||
|  | 	Role      Role     `json:"role" ` | ||||||
|  | 	AuthType  AuthType `json:"authType" ` | ||||||
|  | 	Password  *string  `json:"password" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PageInfo struct { | ||||||
|  | 	Count int `json:"count" ` | ||||||
|  | 	Page  int `json:"page" ` | ||||||
|  | 	Total int `json:"total" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type RoleFilter struct { | ||||||
|  | 	EqualTo    *Role  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo *Role  `json:"notEqualTo" ` | ||||||
|  | 	In         []Role `json:"in" ` | ||||||
|  | 	NotIn      []Role `json:"notIn" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type StringFilter struct { | ||||||
|  | 	EqualTo            *string  `json:"equalTo" ` | ||||||
|  | 	NotEqualTo         *string  `json:"notEqualTo" ` | ||||||
|  | 	StartWith          *string  `json:"startWith" ` | ||||||
|  | 	NotStartWith       *string  `json:"notStartWith" ` | ||||||
|  | 	EndWith            *string  `json:"endWith" ` | ||||||
|  | 	NotEndWith         *string  `json:"notEndWith" ` | ||||||
|  | 	Contain            *string  `json:"contain" ` | ||||||
|  | 	NotContain         *string  `json:"notContain" ` | ||||||
|  | 	In                 []string `json:"in" ` | ||||||
|  | 	NotIn              []string `json:"notIn" ` | ||||||
|  | 	StartWithStrict    *string  `json:"startWithStrict" ` | ||||||
|  | 	NotStartWithStrict *string  `json:"notStartWithStrict" ` | ||||||
|  | 	EndWithStrict      *string  `json:"endWithStrict" ` | ||||||
|  | 	NotEndWithStrict   *string  `json:"notEndWithStrict" ` | ||||||
|  | 	ContainStrict      *string  `json:"containStrict" ` | ||||||
|  | 	NotContainStrict   *string  `json:"notContainStrict" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Tag struct { | ||||||
|  | 	ID        *string    `json:"id" gorm:"primarykey;not null"` | ||||||
|  | 	CreatedAt *time.Time `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *time.Time `json:"updatedAt" ` | ||||||
|  | 	Name      string     `json:"name" gorm:"unique;not null"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TagFilter struct { | ||||||
|  | 	ID        *IDFilter        `json:"id" ` | ||||||
|  | 	CreatedAt *TimeFilter      `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *TimeFilter      `json:"updatedAt" ` | ||||||
|  | 	Name      *StringFilter    `json:"name" ` | ||||||
|  | 	And       *MediaItemFilter `json:"and" ` | ||||||
|  | 	Or        *MediaItemFilter `json:"or" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TagResponse struct { | ||||||
|  | 	Data     []*Tag    `json:"data" ` | ||||||
|  | 	PageInfo *PageInfo `json:"pageInfo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TimeFilter struct { | ||||||
|  | 	EqualTo           *time.Time `json:"equalTo" ` | ||||||
|  | 	NotEqualTo        *time.Time `json:"notEqualTo" ` | ||||||
|  | 	LessThan          *time.Time `json:"lessThan" ` | ||||||
|  | 	LessThanOrEqualTo *time.Time `json:"lessThanOrEqualTo" ` | ||||||
|  | 	MoreThan          *time.Time `json:"moreThan" ` | ||||||
|  | 	MoreThanOrEqualTo *time.Time `json:"moreThanOrEqualTo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type User struct { | ||||||
|  | 	ID        *string    `json:"id" gorm:"primarykey;not null"` | ||||||
|  | 	CreatedAt *time.Time `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *time.Time `json:"updatedAt" ` | ||||||
|  | 	Email     string     `json:"email" gorm:"not null;unique"` | ||||||
|  | 	Username  string     `json:"username" gorm:"not null;unique"` | ||||||
|  | 	FirstName *string    `json:"firstName" ` | ||||||
|  | 	LastName  *string    `json:"lastName" ` | ||||||
|  | 	Role      Role       `json:"role" gorm:"default:User;not null"` | ||||||
|  | 	AuthType  AuthType   `json:"authType" gorm:"default:Local;not null"` | ||||||
|  | 	Password  *string    `json:"password" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UserFilter struct { | ||||||
|  | 	ID        *IDFilter       `json:"id" ` | ||||||
|  | 	CreatedAt *TimeFilter     `json:"createdAt" ` | ||||||
|  | 	UpdatedAt *TimeFilter     `json:"updatedAt" ` | ||||||
|  | 	Username  *StringFilter   `json:"username" ` | ||||||
|  | 	FirstName *StringFilter   `json:"firstName" ` | ||||||
|  | 	LastName  *StringFilter   `json:"lastName" ` | ||||||
|  | 	Role      *RoleFilter     `json:"role" ` | ||||||
|  | 	AuthType  *AuthTypeFilter `json:"authType" ` | ||||||
|  | 	And       *UserFilter     `json:"and" ` | ||||||
|  | 	Or        *UserFilter     `json:"or" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UserResponse struct { | ||||||
|  | 	Data     []*User   `json:"data" ` | ||||||
|  | 	PageInfo *PageInfo `json:"pageInfo" ` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AuthType string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	AuthTypeLocal AuthType = "Local" | ||||||
|  | 	AuthTypeLdap  AuthType = "LDAP" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var AllAuthType = []AuthType{ | ||||||
|  | 	AuthTypeLocal, | ||||||
|  | 	AuthTypeLdap, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e AuthType) IsValid() bool { | ||||||
|  | 	switch e { | ||||||
|  | 	case AuthTypeLocal, AuthTypeLdap: | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e AuthType) String() string { | ||||||
|  | 	return string(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *AuthType) UnmarshalGQL(v interface{}) error { | ||||||
|  | 	str, ok := v.(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return fmt.Errorf("enums must be strings") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*e = AuthType(str) | ||||||
|  | 	if !e.IsValid() { | ||||||
|  | 		return fmt.Errorf("%s is not a valid AuthType", str) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e AuthType) MarshalGQL(w io.Writer) { | ||||||
|  | 	fmt.Fprint(w, strconv.Quote(e.String())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DeviceType string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	DeviceTypeIOs              DeviceType = "iOS" | ||||||
|  | 	DeviceTypeAndroid          DeviceType = "Android" | ||||||
|  | 	DeviceTypeChrome           DeviceType = "Chrome" | ||||||
|  | 	DeviceTypeFirefox          DeviceType = "Firefox" | ||||||
|  | 	DeviceTypeInternetExplorer DeviceType = "InternetExplorer" | ||||||
|  | 	DeviceTypeEdge             DeviceType = "Edge" | ||||||
|  | 	DeviceTypeSafari           DeviceType = "Safari" | ||||||
|  | 	DeviceTypeUnknown          DeviceType = "Unknown" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var AllDeviceType = []DeviceType{ | ||||||
|  | 	DeviceTypeIOs, | ||||||
|  | 	DeviceTypeAndroid, | ||||||
|  | 	DeviceTypeChrome, | ||||||
|  | 	DeviceTypeFirefox, | ||||||
|  | 	DeviceTypeInternetExplorer, | ||||||
|  | 	DeviceTypeEdge, | ||||||
|  | 	DeviceTypeSafari, | ||||||
|  | 	DeviceTypeUnknown, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e DeviceType) IsValid() bool { | ||||||
|  | 	switch e { | ||||||
|  | 	case DeviceTypeIOs, DeviceTypeAndroid, DeviceTypeChrome, DeviceTypeFirefox, DeviceTypeInternetExplorer, DeviceTypeEdge, DeviceTypeSafari, DeviceTypeUnknown: | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e DeviceType) String() string { | ||||||
|  | 	return string(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *DeviceType) UnmarshalGQL(v interface{}) error { | ||||||
|  | 	str, ok := v.(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return fmt.Errorf("enums must be strings") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*e = DeviceType(str) | ||||||
|  | 	if !e.IsValid() { | ||||||
|  | 		return fmt.Errorf("%s is not a valid DeviceType", str) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e DeviceType) MarshalGQL(w io.Writer) { | ||||||
|  | 	fmt.Fprint(w, strconv.Quote(e.String())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Role string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	RoleAdmin Role = "Admin" | ||||||
|  | 	RoleUser  Role = "User" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var AllRole = []Role{ | ||||||
|  | 	RoleAdmin, | ||||||
|  | 	RoleUser, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e Role) IsValid() bool { | ||||||
|  | 	switch e { | ||||||
|  | 	case RoleAdmin, RoleUser: | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e Role) String() string { | ||||||
|  | 	return string(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *Role) UnmarshalGQL(v interface{}) error { | ||||||
|  | 	str, ok := v.(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return fmt.Errorf("enums must be strings") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*e = Role(str) | ||||||
|  | 	if !e.IsValid() { | ||||||
|  | 		return fmt.Errorf("%s is not a valid Role", str) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e Role) MarshalGQL(w io.Writer) { | ||||||
|  | 	fmt.Fprint(w, strconv.Quote(e.String())) | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								graph/resolver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								graph/resolver.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | package graph | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  |     "reichard.io/imagini/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // This file will not be regenerated automatically. | ||||||
|  | // | ||||||
|  | // It serves as dependency injection for your app, add any dependencies you require here. | ||||||
|  | 
 | ||||||
|  | type Resolver struct{ | ||||||
|  |     DB *db.DBManager | ||||||
|  | } | ||||||
							
								
								
									
										356
									
								
								graph/schema.graphqls
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								graph/schema.graphqls
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,356 @@ | |||||||
|  | 
 | ||||||
|  | # https://gqlgen.com/reference/scalars/ | ||||||
|  | scalar Time | ||||||
|  | scalar Upload | ||||||
|  | 
 | ||||||
|  | # https://gqlgen.com/reference/directives/ | ||||||
|  | directive @hasRole(role: Role!) on FIELD_DEFINITION | ||||||
|  | 
 | ||||||
|  | directive @meta( | ||||||
|  |     gorm: String, | ||||||
|  | ) on OBJECT | FIELD_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION | ENUM | INPUT_OBJECT | ARGUMENT_DEFINITION | ||||||
|  | 
 | ||||||
|  | enum Role { | ||||||
|  |     Admin | ||||||
|  |     User | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum DeviceType { | ||||||
|  |     iOS | ||||||
|  |     Android | ||||||
|  |     Chrome | ||||||
|  |     Firefox | ||||||
|  |     InternetExplorer | ||||||
|  |     Edge | ||||||
|  |     Safari | ||||||
|  |     Unknown | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum AuthType { | ||||||
|  |     Local | ||||||
|  |     LDAP | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # ----------------------- Type Filters ----------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | input TimeFilter { | ||||||
|  |     equalTo: Time | ||||||
|  |     notEqualTo: Time | ||||||
|  |     lessThan: Time | ||||||
|  |     lessThanOrEqualTo: Time | ||||||
|  |     moreThan: Time | ||||||
|  |     moreThanOrEqualTo: Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input IntFilter { | ||||||
|  |     equalTo: Int | ||||||
|  |     notEqualTo: Int | ||||||
|  |     lessThan: Int | ||||||
|  |     lessThanOrEqualTo: Int | ||||||
|  |     moreThan: Int | ||||||
|  |     moreThanOrEqualTo: Int | ||||||
|  |     in: [Int!] | ||||||
|  |     notIn: [Int!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input FloatFilter { | ||||||
|  |     equalTo: Float | ||||||
|  |     notEqualTo: Float | ||||||
|  |     lessThan: Float | ||||||
|  |     lessThanOrEqualTo: Float | ||||||
|  |     moreThan: Float | ||||||
|  |     moreThanOrEqualTo: Float | ||||||
|  |     in: [Float!] | ||||||
|  |     notIn: [Float!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input BooleanFilter { | ||||||
|  |     equalTo: Boolean | ||||||
|  |     notEqualTo: Boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input IDFilter { | ||||||
|  |     equalTo: ID | ||||||
|  |     notEqualTo: ID | ||||||
|  |     in: [ID!] | ||||||
|  |     notIn: [ID!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input StringFilter { | ||||||
|  |     equalTo: String | ||||||
|  |     notEqualTo: String | ||||||
|  |     startWith: String | ||||||
|  |     notStartWith: String | ||||||
|  |     endWith: String | ||||||
|  |     notEndWith: String | ||||||
|  |     contain: String | ||||||
|  |     notContain: String | ||||||
|  |     in: [String!] | ||||||
|  |     notIn: [String!] | ||||||
|  | 
 | ||||||
|  |     startWithStrict: String | ||||||
|  |     notStartWithStrict: String | ||||||
|  |     endWithStrict: String | ||||||
|  |     notEndWithStrict: String | ||||||
|  |     containStrict: String | ||||||
|  |     notContainStrict: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input RoleFilter { | ||||||
|  |     equalTo: Role | ||||||
|  |     notEqualTo: Role | ||||||
|  |     in: [Role!] | ||||||
|  |     notIn: [Role!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input DeviceTypeFilter { | ||||||
|  |     equalTo: DeviceType | ||||||
|  |     notEqualTo: DeviceType | ||||||
|  |     in: [DeviceType!] | ||||||
|  |     notIn: [DeviceType!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input AuthTypeFilter { | ||||||
|  |     equalTo: AuthType | ||||||
|  |     notEqualTo: AuthType | ||||||
|  |     in: [AuthType!] | ||||||
|  |     notIn: [AuthType!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # -------------------- Object Definitions -------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | type Device { | ||||||
|  |     id: ID              @meta(gorm: "primarykey;not null") | ||||||
|  |     createdAt: Time | ||||||
|  |     updatedAt: Time | ||||||
|  |     name: String!       @meta(gorm: "not null") | ||||||
|  |     type: DeviceType!   @meta(gorm: "default:Unknown;not null") | ||||||
|  |     user: User          @meta(gorm: "ForeignKey:ID;not null") | ||||||
|  |     refreshKey: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type User { | ||||||
|  |     id: ID              @meta(gorm: "primarykey;not null") | ||||||
|  |     createdAt: Time | ||||||
|  |     updatedAt: Time | ||||||
|  |     email: String!      @meta(gorm: "not null;unique") | ||||||
|  |     username: String!   @meta(gorm: "not null;unique") | ||||||
|  |     firstName: String | ||||||
|  |     lastName: String | ||||||
|  |     role: Role!         @meta(gorm: "default:User;not null") | ||||||
|  |     authType: AuthType! @meta(gorm: "default:Local;not null") | ||||||
|  |     password: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MediaItem { | ||||||
|  |     id: ID              @meta(gorm: "primarykey;not null") | ||||||
|  |     createdAt: Time | ||||||
|  |     updatedAt: Time | ||||||
|  |     exifDate: Time | ||||||
|  |     latitude: Float | ||||||
|  |     longitude: Float | ||||||
|  |     isVideo: Boolean!   @meta(gorm: "default:false;not null") | ||||||
|  |     fileName: String!   @meta(gorm: "not null") | ||||||
|  |     origName: String!   @meta(gorm: "not null") | ||||||
|  |     tags: [Tag]         @meta(gorm: "many2many:media_tags") | ||||||
|  |     albums: [Album]     @meta(gorm: "many2many:media_albums") | ||||||
|  |     user: User          @meta(gorm: "ForeignKey:ID;not null") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Tag { | ||||||
|  |     id: ID          @meta(gorm: "primarykey;not null") | ||||||
|  |     createdAt: Time | ||||||
|  |     updatedAt: Time | ||||||
|  |     name: String!   @meta(gorm: "unique;not null") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Album { | ||||||
|  |     id: ID          @meta(gorm: "primarykey;not null") | ||||||
|  |     createdAt: Time | ||||||
|  |     updatedAt: Time | ||||||
|  |     name: String!   @meta(gorm: "unique;not null") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # ---------------------- Object Filters ---------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | input UserFilter { | ||||||
|  |     id: IDFilter | ||||||
|  |     createdAt: TimeFilter | ||||||
|  |     updatedAt: TimeFilter | ||||||
|  |     username: StringFilter | ||||||
|  |     firstName: StringFilter | ||||||
|  |     lastName: StringFilter | ||||||
|  |     role: RoleFilter | ||||||
|  |     authType: AuthTypeFilter | ||||||
|  | 
 | ||||||
|  |     and: UserFilter | ||||||
|  |     or: UserFilter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input MediaItemFilter { | ||||||
|  |     id: IDFilter | ||||||
|  |     createdAt: TimeFilter | ||||||
|  |     updatedAt: TimeFilter | ||||||
|  |     exifDate: TimeFilter | ||||||
|  |     latitude: FloatFilter | ||||||
|  |     longitude: FloatFilter | ||||||
|  |     isVideo: BooleanFilter | ||||||
|  |     origName: StringFilter | ||||||
|  |     tags: TagFilter | ||||||
|  |     albums: AlbumFilter | ||||||
|  | 
 | ||||||
|  |     and: MediaItemFilter | ||||||
|  |     or: MediaItemFilter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input DeviceFilter { | ||||||
|  |     id: IDFilter | ||||||
|  |     createdAt: TimeFilter | ||||||
|  |     updatedAt: TimeFilter | ||||||
|  |     name: StringFilter | ||||||
|  |     type: DeviceTypeFilter | ||||||
|  | 
 | ||||||
|  |     and: MediaItemFilter | ||||||
|  |     or: MediaItemFilter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input TagFilter { | ||||||
|  |     id: IDFilter | ||||||
|  |     createdAt: TimeFilter | ||||||
|  |     updatedAt: TimeFilter | ||||||
|  |     name: StringFilter | ||||||
|  | 
 | ||||||
|  |     and: MediaItemFilter | ||||||
|  |     or: MediaItemFilter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input AlbumFilter { | ||||||
|  |     id: IDFilter | ||||||
|  |     createdAt: TimeFilter | ||||||
|  |     updatedAt: TimeFilter | ||||||
|  |     name: StringFilter | ||||||
|  | 
 | ||||||
|  |     and: MediaItemFilter | ||||||
|  |     or: MediaItemFilter | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # -------------------------- Inputs -------------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | input NewUser { | ||||||
|  |     email: String! | ||||||
|  |     username: String! | ||||||
|  |     firstName: String | ||||||
|  |     lastName: String | ||||||
|  |     role: Role! | ||||||
|  |     authType: AuthType! | ||||||
|  |     password: String | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input NewDevice { | ||||||
|  |     name: String! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input NewMediaItem { | ||||||
|  |     file: Upload! | ||||||
|  |     tags: [ID!] | ||||||
|  |     albums: [ID!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input NewTag { | ||||||
|  |     name: String! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input NewAlbum { | ||||||
|  |     name: String! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # ------------------------ Responses ------------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | type PageInfo { | ||||||
|  |     count: Int! | ||||||
|  |     page: Int! | ||||||
|  |     total: Int! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MediaItemResponse { | ||||||
|  |     data: [MediaItem] | ||||||
|  |     pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UserResponse { | ||||||
|  |     data: [User] | ||||||
|  |     pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type DeviceResponse { | ||||||
|  |     data: [Device] | ||||||
|  |     pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TagResponse { | ||||||
|  |     data: [Tag] | ||||||
|  |     pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AlbumResponse { | ||||||
|  |     data: [Album] | ||||||
|  |     pageInfo: PageInfo! | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | # --------------------- Query & Mutations -------------------- | ||||||
|  | # ------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | type Query { | ||||||
|  |     # Single Item | ||||||
|  |     mediaItem(id: ID!): MediaItem!  @hasRole(role: User) | ||||||
|  |     device(id: ID!): Device!        @hasRole(role: User) | ||||||
|  |     album(id: ID!): Album!          @hasRole(role: User) | ||||||
|  |     tag(id: ID!): Tag!              @hasRole(role: User) | ||||||
|  |     user(id: ID!): User!            @hasRole(role: Admin) | ||||||
|  | 
 | ||||||
|  |     # All | ||||||
|  |     mediaItems( | ||||||
|  |         filter: MediaItemFilter | ||||||
|  |         count: Int | ||||||
|  |         page: Int | ||||||
|  |     ): MediaItemResponse!           @hasRole(role: User) | ||||||
|  |     devices( | ||||||
|  |         filter: DeviceFilter | ||||||
|  |         count: Int | ||||||
|  |         page: Int | ||||||
|  |     ): DeviceResponse!              @hasRole(role: User) | ||||||
|  |     albums( | ||||||
|  |         filter: AlbumFilter | ||||||
|  |         count: Int | ||||||
|  |         page: Int | ||||||
|  |     ): AlbumResponse!               @hasRole(role: User) | ||||||
|  |     tags( | ||||||
|  |         filter: TagFilter | ||||||
|  |         count: Int | ||||||
|  |         page: Int | ||||||
|  |     ): TagResponse!                 @hasRole(role: User) | ||||||
|  |     users( | ||||||
|  |         filter: UserFilter | ||||||
|  |         count: Int | ||||||
|  |         page: Int | ||||||
|  |     ): UserResponse!                @hasRole(role: Admin) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Mutation { | ||||||
|  |     createMediaItem(input: NewMediaItem!): MediaItem!   @hasRole(role: User) | ||||||
|  |     createDevice(input: NewDevice!): Device!            @hasRole(role: User) | ||||||
|  |     createAlbum(input: NewAlbum!): Album!               @hasRole(role: User) | ||||||
|  |     createTag(input: NewTag!): Tag!                     @hasRole(role: User) | ||||||
|  |     createUser(input: NewUser!): User!                  @hasRole(role: Admin) | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								graph/schema.resolvers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								graph/schema.resolvers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | package graph | ||||||
|  | 
 | ||||||
|  | // This file will be automatically regenerated based on the schema, any resolver implementations | ||||||
|  | // will be copied through when generating and any unknown code will be moved to the end. | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"reichard.io/imagini/graph/generated" | ||||||
|  | 	"reichard.io/imagini/graph/model" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *mutationResolver) CreateDevice(ctx context.Context, input model.NewDevice) (*model.Device, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *mutationResolver) CreateTag(ctx context.Context, input model.NewTag) (*model.Tag, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) MediaItem(ctx context.Context, id string) (*model.MediaItem, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Device(ctx context.Context, id string) (*model.Device, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Album(ctx context.Context, id string) (*model.Album, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) MediaItems(ctx context.Context, filter *model.MediaItemFilter, count *int, page *int) (*model.MediaItemResponse, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Devices(ctx context.Context, filter *model.DeviceFilter, count *int, page *int) (*model.DeviceResponse, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Albums(ctx context.Context, filter *model.AlbumFilter, count *int, page *int) (*model.AlbumResponse, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Tags(ctx context.Context, filter *model.TagFilter, count *int, page *int) (*model.TagResponse, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *queryResolver) Users(ctx context.Context, filter *model.UserFilter, count *int, page *int) (*model.UserResponse, error) { | ||||||
|  | 	panic(fmt.Errorf("not implemented")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Mutation returns generated.MutationResolver implementation. | ||||||
|  | func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } | ||||||
|  | 
 | ||||||
|  | // Query returns generated.QueryResolver implementation. | ||||||
|  | func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } | ||||||
|  | 
 | ||||||
|  | type mutationResolver struct{ *Resolver } | ||||||
|  | type queryResolver struct{ *Resolver } | ||||||
| @ -1,9 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) albumsHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -11,6 +11,7 @@ import ( | |||||||
|     "github.com/lestrrat-go/jwx/jwt" |     "github.com/lestrrat-go/jwx/jwt" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
|  |     graphql "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) { | func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) { | ||||||
| @ -81,47 +82,47 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| /** | /** | ||||||
|  * This will find or create the requested device based on ID and User. |  * This will find or create the requested device based on ID and User. | ||||||
|  **/ |  **/ | ||||||
| func (api *API) upsertRequestedDevice(user models.User, r *http.Request) (models.Device, error) { | func (api *API) upsertRequestedDevice(user graphql.User, r *http.Request) (graphql.Device, error) { | ||||||
|     requestedDevice := deriveRequestedDevice(r) |     requestedDevice := deriveRequestedDevice(r) | ||||||
|     requestedDevice.Type = deriveDeviceType(r) |     requestedDevice.Type = deriveDeviceType(r) | ||||||
|     requestedDevice.UserUUID = user.UUID |     requestedDevice.User.ID = user.ID | ||||||
| 
 | 
 | ||||||
|     if requestedDevice.UUID == uuid.Nil { |     if *requestedDevice.ID == "" { | ||||||
|         err := api.DB.CreateDevice(&requestedDevice) |         err := api.DB.CreateDevice(&requestedDevice) | ||||||
|         createdDevice, err := api.DB.Device(&requestedDevice) |         createdDevice, err := api.DB.Device(&requestedDevice) | ||||||
|         return createdDevice, err |         return createdDevice, err | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     foundDevice, err := api.DB.Device(&models.Device{ |     foundDevice, err := api.DB.Device(&graphql.Device{ | ||||||
|         Base: models.Base{ UUID: requestedDevice.UUID }, |         ID: requestedDevice.ID, | ||||||
|         User: user, |         User: &user, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     return foundDevice, err |     return foundDevice, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func deriveDeviceType(r *http.Request) string { | func deriveDeviceType(r *http.Request) graphql.DeviceType { | ||||||
|     userAgent := strings.ToLower(r.Header.Get("User-Agent")) |     userAgent := strings.ToLower(r.Header.Get("User-Agent")) | ||||||
|     if strings.HasPrefix(userAgent, "ios-imagini"){ |     if strings.HasPrefix(userAgent, "ios-imagini"){ | ||||||
|         return "iOS" |         return graphql.DeviceTypeIOs | ||||||
|     } else if strings.HasPrefix(userAgent, "android-imagini"){ |     } else if strings.HasPrefix(userAgent, "android-imagini"){ | ||||||
|         return "Android" |         return graphql.DeviceTypeAndroid | ||||||
|     } else if strings.HasPrefix(userAgent, "chrome"){ |     } else if strings.HasPrefix(userAgent, "chrome"){ | ||||||
|         return "Chrome" |         return graphql.DeviceTypeChrome | ||||||
|     } else if strings.HasPrefix(userAgent, "firefox"){ |     } else if strings.HasPrefix(userAgent, "firefox"){ | ||||||
|         return "Firefox" |         return graphql.DeviceTypeFirefox | ||||||
|     } else if strings.HasPrefix(userAgent, "msie"){ |     } else if strings.HasPrefix(userAgent, "msie"){ | ||||||
|         return "Internet Explorer" |         return graphql.DeviceTypeInternetExplorer | ||||||
|     } else if strings.HasPrefix(userAgent, "edge"){ |     } else if strings.HasPrefix(userAgent, "edge"){ | ||||||
|         return "Edge" |         return graphql.DeviceTypeEdge | ||||||
|     } else if strings.HasPrefix(userAgent, "safari"){ |     } else if strings.HasPrefix(userAgent, "safari"){ | ||||||
|         return "Safari" |         return graphql.DeviceTypeSafari | ||||||
|     } |     } | ||||||
|     return "Unknown" |     return graphql.DeviceTypeUnknown | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func deriveRequestedDevice(r *http.Request) models.Device { | func deriveRequestedDevice(r *http.Request) graphql.Device { | ||||||
|     deviceSkeleton := models.Device{} |     deviceSkeleton := graphql.Device{} | ||||||
|     authHeader := r.Header.Get("X-Imagini-Authorization") |     authHeader := r.Header.Get("X-Imagini-Authorization") | ||||||
|     splitAuthInfo := strings.Split(authHeader, ",") |     splitAuthInfo := strings.Split(authHeader, ",") | ||||||
| 
 | 
 | ||||||
| @ -137,19 +138,20 @@ func deriveRequestedDevice(r *http.Request) models.Device { | |||||||
| 
 | 
 | ||||||
|         // Derive Key |         // Derive Key | ||||||
|         key := strings.ToLower(strings.TrimSpace(splitItem[0])) |         key := strings.ToLower(strings.TrimSpace(splitItem[0])) | ||||||
|         if key != "deviceuuid" && key != "devicename" { |         if key != "deviceid" && key != "devicename" { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Derive Value |         // Derive Value | ||||||
|         val := trimQuotes(strings.TrimSpace(splitItem[1])) |         val := trimQuotes(strings.TrimSpace(splitItem[1])) | ||||||
|         if key == "deviceuuid" { |         if key == "deviceid" { | ||||||
|             parsedDeviceUUID, err := uuid.Parse(val) |             parsedDeviceUUID, err := uuid.Parse(val) | ||||||
|             if err != nil { |             if err != nil { | ||||||
|                 log.Warn("[auth] deriveRequestedDevice - Unable to parse requested DeviceUUID: ", val) |                 log.Warn("[auth] deriveRequestedDevice - Unable to parse requested DeviceUUID: ", val) | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             deviceSkeleton.Base = models.Base{UUID: parsedDeviceUUID} |             stringDeviceUUID := parsedDeviceUUID.String() | ||||||
|  |             deviceSkeleton.ID = &stringDeviceUUID | ||||||
|         } else if key == "devicename" { |         } else if key == "devicename" { | ||||||
|             deviceSkeleton.Name = val |             deviceSkeleton.Name = val | ||||||
|         } |         } | ||||||
| @ -157,7 +159,7 @@ func deriveRequestedDevice(r *http.Request) models.Device { | |||||||
| 
 | 
 | ||||||
|     // If name not set, set to type |     // If name not set, set to type | ||||||
|     if deviceSkeleton.Name == "" { |     if deviceSkeleton.Name == "" { | ||||||
|         deviceSkeleton.Name = deviceSkeleton.Type |         deviceSkeleton.Name = deviceSkeleton.Type.String() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return deviceSkeleton |     return deviceSkeleton | ||||||
| @ -196,9 +198,12 @@ func (api *API) refreshAccessToken(w http.ResponseWriter, r *http.Request) (jwt. | |||||||
|         return nil, err |         return nil, err | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     stringUserUUID := userUUID.String() | ||||||
|  |     stringDeviceUUID := deviceUUID.String() | ||||||
|  | 
 | ||||||
|     // Device & User Skeleton |     // Device & User Skeleton | ||||||
|     user := models.User{Base: models.Base{UUID: userUUID}} |     user := graphql.User{ID: &stringUserUUID} | ||||||
|     device := models.Device{Base: models.Base{UUID: deviceUUID}} |     device := graphql.Device{ID: &stringDeviceUUID} | ||||||
| 
 | 
 | ||||||
|     // Update token |     // Update token | ||||||
|     accessTokenString, err := api.Auth.CreateJWTAccessToken(user, device) |     accessTokenString, err := api.Auth.CreateJWTAccessToken(user, device) | ||||||
|  | |||||||
| @ -1,9 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) devicesHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) infoHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,327 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"fmt" |  | ||||||
| 	"path" |  | ||||||
| 	"time" |  | ||||||
| 	"regexp" |  | ||||||
| 	"strings" |  | ||||||
|     "errors" |  | ||||||
| 	"net/url" |  | ||||||
| 	"net/http" |  | ||||||
| 	"encoding/json" |  | ||||||
| 
 |  | ||||||
| 	"github.com/google/uuid" |  | ||||||
| 	"github.com/dsoprea/go-exif/v3" |  | ||||||
| 	log "github.com/sirupsen/logrus" |  | ||||||
| 	"github.com/gabriel-vasile/mimetype" |  | ||||||
| 	"github.com/dsoprea/go-exif/v3/common" |  | ||||||
| 
 |  | ||||||
| 	"reichard.io/imagini/internal/models" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // GET |  | ||||||
| //  - /api/v1/MediaItems/<GUID> |  | ||||||
| //      - JSON Struct |  | ||||||
| //  - /api/v1/MediaItems/<GUID>/content |  | ||||||
| //      - The raw file |  | ||||||
| func (api *API) mediaItemsHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     if r.Method == http.MethodPost { |  | ||||||
|         // CREATE |  | ||||||
|         api.mediaItemPOSTHandler(w, r) |  | ||||||
|     } else if r.Method == http.MethodPut { |  | ||||||
|         // UPDATE / REPLACE |  | ||||||
|     } else if r.Method == http.MethodPatch { |  | ||||||
|         // UPDATE / MODIFY |  | ||||||
|     } else if r.Method == http.MethodDelete { |  | ||||||
|         // DELETE |  | ||||||
|     } else if r.Method == http.MethodGet { |  | ||||||
|         // GET |  | ||||||
|         api.mediaItemGETHandler(w, r) |  | ||||||
|     } else { |  | ||||||
|         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Paging: |  | ||||||
| //  - Regular Pagination: |  | ||||||
| //      - /api/v1/MediaItems?page[limit]=50&page=2 |  | ||||||
| //  - Meta Count Only |  | ||||||
| //      - /api/v1/MediaItems?page[limit]=0 |  | ||||||
| 
 |  | ||||||
| // Sorting: |  | ||||||
| //  - Ascending Sort: |  | ||||||
| //      - /api/v1/MediaItems?sort=created_at |  | ||||||
| //  - Descending Sort: |  | ||||||
| //      - /api/v1/MediaItems?sort=-created_at |  | ||||||
| 
 |  | ||||||
| // Filters: |  | ||||||
| //  - Greater Than / Less Than (created_at, updated_at, exif_date) |  | ||||||
| //      - /api/v1/MediaItems?filter[created_at]>=2020-01-01&filter[created_at]<=2021-01-01 |  | ||||||
| //  - Long / Lat Range (latitude, longitude) |  | ||||||
| //      - /api/v1/MediaItems?filter[latitude]>=71.1827&filter[latitude]<=72.0000&filter[longitude]>=100.000&filter[longitude]<=101.0000 |  | ||||||
| //  - Image / Video (media_type) |  | ||||||
| //      - /api/v1/MediaItems?filter[media_type]=Image |  | ||||||
| //  - Tags (tags) |  | ||||||
| //      - /api/v1/MediaItems?filter[tags]=id1,id2,id3 |  | ||||||
| //  - Albums (albums) |  | ||||||
| //      - /api/v1/MediaItems?filter[albums]=id1 |  | ||||||
| func (api *API) mediaItemGETHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     if err := r.ParseForm(); err != nil { |  | ||||||
|         // Handle error |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     testObj := models.MediaItem{} |  | ||||||
|     json.NewDecoder().Decode(&testObj) |  | ||||||
|     fmt.Printf("Result: %+v\n", testObj) |  | ||||||
| 
 |  | ||||||
|     // allParams, err := json.Marshal(r.Form) |  | ||||||
|     // if err != nil { |  | ||||||
|     //     // Handle error |  | ||||||
|     // } |  | ||||||
| 
 |  | ||||||
|     // filter := &models.MediaItem{} |  | ||||||
|     // if err = json.Unmarshal(allParams, filter); err != nil { |  | ||||||
|     //     // Handle error |  | ||||||
|     //     fmt.Printf("Fuck: %s\n", err) |  | ||||||
|     // } |  | ||||||
| 
 |  | ||||||
|     // fmt.Printf("Result: %+v\n", filter) |  | ||||||
| 
 |  | ||||||
|     // err = normalizeForm(r.Form, models.MediaItem{}) |  | ||||||
|     // if err != nil { |  | ||||||
|     //     fmt.Printf("Error: %s\n", err) |  | ||||||
|     // } |  | ||||||
| 
 |  | ||||||
|     // var testItems []models.MediaItem |  | ||||||
|     // api.DB.QueryBuilder(&testItems, allParams) |  | ||||||
| 
 |  | ||||||
|     // fmt.Printf("\n\nItems: %+v", testItems) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     // Pull out UUIDs |  | ||||||
|     reqInfo := r.Context().Value("uuids").(map[string]string) |  | ||||||
|     uid := reqInfo["uid"] |  | ||||||
|     userUUID, _ := uuid.Parse(uid) |  | ||||||
| 
 |  | ||||||
|     // TODO: Can apply multiple filters based on query parameters |  | ||||||
|     mediaItemFilter := &models.MediaItem{UserUUID: userUUID} |  | ||||||
|     mediaItemFilter.UserUUID = userUUID |  | ||||||
| 
 |  | ||||||
|     mediaItems, count, _ := api.DB.MediaItems(mediaItemFilter) |  | ||||||
|     response := &models.APIResponse{ |  | ||||||
|         Data: &mediaItems, |  | ||||||
|         Meta: &models.APIMeta{Count: count}, |  | ||||||
|     } |  | ||||||
|     responseJSON(w, &response, http.StatusOK) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (api *API) mediaItemPOSTHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     // 64MB limit (TODO: Change this - video) |  | ||||||
|     r.ParseMultipartForm(64 << 20) |  | ||||||
| 
 |  | ||||||
|     // Open form file |  | ||||||
|     formFile, multipartFileHeader, err := r.FormFile("file") |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
|     defer formFile.Close() |  | ||||||
| 
 |  | ||||||
|     // File header placeholder |  | ||||||
|     fileHeader := make([]byte, 64) |  | ||||||
| 
 |  | ||||||
|     // Copy headers into the buffer |  | ||||||
|     if _, err := formFile.Read(fileHeader); err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Reset position |  | ||||||
|     if _, err := formFile.Seek(0, 0); err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Determine media type |  | ||||||
|     fileMime := mimetype.Detect(fileHeader) |  | ||||||
|     contentType := fileMime.String() |  | ||||||
|     var mediaType string |  | ||||||
|     if strings.HasPrefix(contentType, "image/") { |  | ||||||
|         mediaType = "Image" |  | ||||||
|     } else if strings.HasPrefix(contentType, "video/") { |  | ||||||
|         mediaType = "Video" |  | ||||||
|     } else { |  | ||||||
|         errorJSON(w, "Invalid filetype.", http.StatusUnsupportedMediaType) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Pull out UUIDs |  | ||||||
|     reqInfo := r.Context().Value("uuids").(map[string]string) |  | ||||||
|     uid := reqInfo["uid"] |  | ||||||
| 
 |  | ||||||
|     // Derive Folder & File Path |  | ||||||
|     mediaItemUUID := uuid.New() |  | ||||||
|     fileName := mediaItemUUID.String() + fileMime.Extension() |  | ||||||
|     folderPath := path.Join("/" + api.Config.DataPath + "/media/" + uid) |  | ||||||
|     os.MkdirAll(folderPath, 0700) |  | ||||||
|     filePath := path.Join(folderPath + "/" + fileName) |  | ||||||
| 
 |  | ||||||
|     // Create File |  | ||||||
|     f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0600) |  | ||||||
|     if err != nil { |  | ||||||
|         log.Warn("[api] createMediaItem - Unable to open file: ", filePath) |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
|     defer f.Close() |  | ||||||
| 
 |  | ||||||
|     // Copy data to file |  | ||||||
|     _, err = io.Copy(f, formFile) |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Create MediaItem From EXIF Data |  | ||||||
|     mediaItem, err := mediaItemFromEXIFData(filePath) |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Add Additional MediaItem Fields |  | ||||||
|     mediaItem.Base.UUID = mediaItemUUID |  | ||||||
|     mediaItem.UserUUID, err = uuid.Parse(uid) |  | ||||||
|     mediaItem.MediaType = mediaType |  | ||||||
|     mediaItem.FileName = fileName |  | ||||||
|     mediaItem.OrigName = multipartFileHeader.Filename |  | ||||||
| 
 |  | ||||||
|     // Create MediaItem in DB |  | ||||||
|     err = api.DB.CreateMediaItem(mediaItem) |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     successJSON(w, "Upload succeeded.", http.StatusCreated) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func mediaItemFromEXIFData(filePath string) (*models.MediaItem, error) { |  | ||||||
|     rawExif, err := exif.SearchFileAndExtractExif(filePath) |  | ||||||
|     entries, _, err := exif.GetFlatExifData(rawExif, nil) |  | ||||||
| 
 |  | ||||||
|     decLong := float32(1) |  | ||||||
|     decLat := float32(1) |  | ||||||
| 
 |  | ||||||
|     mediaItem := &models.MediaItem{} |  | ||||||
|     for _, v := range entries { |  | ||||||
|         if v.TagName == "DateTimeOriginal" { |  | ||||||
|             formattedTime, _ := time.Parse("2006:01:02 15:04:05", v.Formatted) |  | ||||||
|             mediaItem.EXIFDate = formattedTime |  | ||||||
|         } else if v.TagName == "GPSLatitude" { |  | ||||||
|             latStruct := v.Value.([]exifcommon.Rational) |  | ||||||
|             decLat *= deriveDecimalCoordinate( |  | ||||||
|                 latStruct[0].Numerator / latStruct[0].Denominator, |  | ||||||
|                 latStruct[1].Numerator / latStruct[1].Denominator, |  | ||||||
|                 float32(latStruct[2].Numerator) / float32(latStruct[2].Denominator), |  | ||||||
|             ) |  | ||||||
|         } else if v.TagName == "GPSLongitude" { |  | ||||||
|             longStruct := v.Value.([]exifcommon.Rational) |  | ||||||
|             decLong *= deriveDecimalCoordinate( |  | ||||||
|                 longStruct[0].Numerator / longStruct[0].Denominator, |  | ||||||
|                 longStruct[1].Numerator / longStruct[1].Denominator, |  | ||||||
|                 float32(longStruct[2].Numerator) / float32(longStruct[2].Denominator), |  | ||||||
|             ) |  | ||||||
|         } else if v.TagName == "GPSLatitudeRef" && v.Formatted == "S" { |  | ||||||
|             decLat *= -1 |  | ||||||
|         } else if v.TagName == "GPSLongitudeRef" && v.Formatted == "W" { |  | ||||||
|             decLong *= -1 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     mediaItem.Latitude = &decLat |  | ||||||
|     mediaItem.Longitude = &decLong |  | ||||||
| 
 |  | ||||||
|     return mediaItem, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func deriveDecimalCoordinate(degrees, minutes uint32, seconds float32) float32 { |  | ||||||
|     return float32(degrees) + (float32(minutes) / 60) + (seconds / 3600) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // { |  | ||||||
| //  filters: [ |  | ||||||
| //      { field: "", operator: ""}, |  | ||||||
| //      { field: "", operator: ""}, |  | ||||||
| //      { field: "", operator: ""}, |  | ||||||
| //  ], |  | ||||||
| //  sort: "" |  | ||||||
| //  page: {} |  | ||||||
| // } |  | ||||||
| func normalizeForm(form url.Values, typeObj interface{}) error { |  | ||||||
|     allowedFields := models.JSONFields(typeObj) |  | ||||||
| 
 |  | ||||||
|     for key, val := range form { |  | ||||||
|         key = strings.ToLower(key) |  | ||||||
| 
 |  | ||||||
|         re := regexp.MustCompile(`^(filter|page)\[(\w*)]($|>|<)$`) |  | ||||||
|         matches := re.FindStringSubmatch(key) |  | ||||||
| 
 |  | ||||||
|         if len(matches) == 4 { |  | ||||||
|             cmd := strings.ToLower(matches[1]) |  | ||||||
|             field := strings.ToLower(matches[2]) |  | ||||||
|             operator := strings.ToLower(matches[3]) |  | ||||||
| 
 |  | ||||||
|             if cmd == "page" && field == "limit" { |  | ||||||
|                 fmt.Printf("cmd: %s field: %s op: %s\n", cmd, field, operator) |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // Validate field |  | ||||||
|             _, ok := allowedFields[field] |  | ||||||
|             if !ok { |  | ||||||
|                 return errors.New("Invalid field.") |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // Val assertions |  | ||||||
|             tempObj := make(map[string]string) |  | ||||||
|             tempObj[field] = val[0] |  | ||||||
| 
 |  | ||||||
|             mi, err := json.Marshal(tempObj) |  | ||||||
|             if err != nil { |  | ||||||
|                 // Handle error |  | ||||||
|                 fmt.Printf("1 Type Assertion Failed For Field: [%s] with value: [%s]\n", field, val) |  | ||||||
|             } |  | ||||||
|             fmt.Printf("String JSON: %s", string(mi)) |  | ||||||
|             refObj := &models.MediaItem{} |  | ||||||
|             if err = json.Unmarshal(mi, refObj); err != nil { |  | ||||||
|                 // Handle error |  | ||||||
|                 fmt.Printf("2 Type Assertion Failed For Field: [%s] with value: [%s]\n", field, val[0]) |  | ||||||
|                 fmt.Println(err) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             fmt.Printf("Result: %+v\n", refObj) |  | ||||||
| 
 |  | ||||||
|             fmt.Printf("cmd: %s field: %s op: %s\n", cmd, field, operator) |  | ||||||
|         } else if key == "sort" { |  | ||||||
|             field := strings.ToLower(val[0]) |  | ||||||
| 
 |  | ||||||
|             // Validate field |  | ||||||
|             _, ok := allowedFields[field] |  | ||||||
|             if !ok { |  | ||||||
|                 return errors.New("Invalid field.") |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // TODO: Validate val |  | ||||||
| 
 |  | ||||||
|             fmt.Printf("cmd: %s\n", key) |  | ||||||
|         } else { |  | ||||||
|             return errors.New("Invalid parameter(s)") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return nil |  | ||||||
| } |  | ||||||
| @ -5,53 +5,27 @@ import ( | |||||||
|     "net/http" |     "net/http" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
|  | 	"github.com/99designs/gqlgen/graphql/handler" | ||||||
|  | 	"github.com/99designs/gqlgen/graphql/playground" | ||||||
|  | 	"reichard.io/imagini/graph" | ||||||
|  | 	"reichard.io/imagini/graph/generated" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (api *API) registerRoutes() { | func (api *API) registerRoutes() { | ||||||
|  | 	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}})) | ||||||
|  | 
 | ||||||
|  |     // TODO: Provide Authentication for srv | ||||||
|  | 	api.Router.Handle("/playground", playground.Handler("GraphQL playground", "/query")) | ||||||
|  | 	api.Router.Handle("/query", srv) | ||||||
|  | 
 | ||||||
|     api.Router.HandleFunc("/media/", multipleMiddleware( |     api.Router.HandleFunc("/media/", multipleMiddleware( | ||||||
|         api.mediaHandler, |         api.mediaHandler, | ||||||
|         api.authMiddleware, |         api.authMiddleware, | ||||||
|     )) |     )) | ||||||
|     api.Router.HandleFunc("/api/v1/MediaItems", multipleMiddleware( |     api.Router.HandleFunc("/logout", api.logoutHandler) | ||||||
|         api.mediaItemsHandler, |     api.Router.HandleFunc("/login",  api.loginHandler) | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Devices",    multipleMiddleware( |  | ||||||
|         api.devicesHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Upload",     multipleMiddleware( |  | ||||||
|         api.uploadHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Albums",     multipleMiddleware( |  | ||||||
|         api.albumsHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Users",      multipleMiddleware( |  | ||||||
|         api.usersHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Tags",       multipleMiddleware( |  | ||||||
|         api.tagsHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Info",       multipleMiddleware( |  | ||||||
|         api.infoHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Me",         multipleMiddleware( |  | ||||||
|         api.meHandler, |  | ||||||
|         api.authMiddleware, |  | ||||||
|     )) |  | ||||||
| 
 |  | ||||||
|     api.Router.HandleFunc("/api/v1/Logout", api.logoutHandler) |  | ||||||
|     api.Router.HandleFunc("/api/v1/Login",  api.loginHandler) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // https://stackoverflow.com/a/59764037 |  | ||||||
| func errorJSON(w http.ResponseWriter, err string, code int) { | func errorJSON(w http.ResponseWriter, err string, code int) { | ||||||
|     errStruct := &models.APIResponse{Error: &models.APIError{Message: err, Code: int64(code)}} |     errStruct := &models.APIResponse{Error: &models.APIError{Message: err, Code: int64(code)}} | ||||||
|     responseJSON(w, errStruct, code) |     responseJSON(w, errStruct, code) | ||||||
|  | |||||||
| @ -1,9 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) tagsHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) uploadHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| package api |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "net/http" |  | ||||||
|     // log "github.com/sirupsen/logrus" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func (api *API) usersHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     if r.Method == http.MethodPost { |  | ||||||
|         // CREATE |  | ||||||
|     } else if r.Method == http.MethodPut { |  | ||||||
|         // UPDATE / REPLACE |  | ||||||
|     } else if r.Method == http.MethodPatch { |  | ||||||
|         // UPDATE / MODIFY |  | ||||||
|     } else if r.Method == http.MethodDelete { |  | ||||||
|         // DELETE |  | ||||||
|     } else if r.Method == http.MethodGet { |  | ||||||
|         // GET |  | ||||||
|     } else { |  | ||||||
|         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (api *API) meHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     if r.Method != http.MethodGet { |  | ||||||
|         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -14,6 +14,8 @@ import ( | |||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/db" |     "reichard.io/imagini/internal/db" | ||||||
|     "reichard.io/imagini/internal/config" |     "reichard.io/imagini/internal/config" | ||||||
|  | 
 | ||||||
|  |     graphql "reichard.io/imagini/graph/model" | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
|     "reichard.io/imagini/internal/session" |     "reichard.io/imagini/internal/session" | ||||||
| ) | ) | ||||||
| @ -33,11 +35,16 @@ func NewMgr(db *db.DBManager, c *config.Config) *AuthManager { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, models.User) { | func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, graphql.User) { | ||||||
|     // By Username |     // Search Objects | ||||||
|     foundUser, err := auth.DB.User(&models.User{Username: creds.User}) |     userByName := &graphql.User{} | ||||||
|  |     userByName.Username = creds.User | ||||||
|  | 
 | ||||||
|  |     foundUser, err := auth.DB.User(userByName) | ||||||
|     if errors.Is(err, gorm.ErrRecordNotFound) { |     if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
|         foundUser, err = auth.DB.User(&models.User{Email: creds.User}) |         userByEmail := &graphql.User{} | ||||||
|  |         userByEmail.Email = creds.User | ||||||
|  |         foundUser, err = auth.DB.User(userByEmail) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Error Checking |     // Error Checking | ||||||
| @ -62,7 +69,7 @@ func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, mo | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) getRole(user models.User) string { | func (auth *AuthManager) getRole(user graphql.User) string { | ||||||
|     // TODO: Lookup role of user |     // TODO: Lookup role of user | ||||||
|     return "User" |     return "User" | ||||||
| } | } | ||||||
| @ -80,7 +87,8 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, | |||||||
|     if err != nil { |     if err != nil { | ||||||
|         return nil, errors.New("did does not parse") |         return nil, errors.New("did does not parse") | ||||||
|     } |     } | ||||||
|     device, err := auth.DB.Device(&models.Device{Base: models.Base{UUID: deviceID}}) |     stringDeviceID := deviceID.String() | ||||||
|  |     device, err := auth.DB.Device(&graphql.Device{ID: &stringDeviceID}) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         return nil, err |         return nil, err | ||||||
|     } |     } | ||||||
| @ -88,7 +96,7 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, | |||||||
|     // Verify & Validate Token |     // Verify & Validate Token | ||||||
|     verifiedToken, err := jwt.ParseBytes(byteRefreshJWT, |     verifiedToken, err := jwt.ParseBytes(byteRefreshJWT, | ||||||
|         jwt.WithValidate(true), |         jwt.WithValidate(true), | ||||||
|         jwt.WithVerify(jwa.HS256, []byte(device.RefreshKey)), |         jwt.WithVerify(jwa.HS256, []byte(*device.RefreshKey)), | ||||||
|     ) |     ) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         fmt.Println("failed to parse payload: ", err) |         fmt.Println("failed to parse payload: ", err) | ||||||
| @ -111,15 +119,15 @@ func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, er | |||||||
|     return verifiedToken, nil |     return verifiedToken, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.Device) (string, error) { | func (auth *AuthManager) CreateJWTRefreshToken(user graphql.User, device graphql.Device) (string, error) { | ||||||
|     // Acquire Refresh Key |     // Acquire Refresh Key | ||||||
|     byteKey := []byte(device.RefreshKey) |     byteKey := []byte(*device.RefreshKey) | ||||||
| 
 | 
 | ||||||
|     // Create New Token |     // Create New Token | ||||||
|     tm := time.Now() |     tm := time.Now() | ||||||
|     t := jwt.New() |     t := jwt.New() | ||||||
|     t.Set(`did`, device.UUID.String())          // Device ID |     t.Set(`did`, device.ID)          // Device ID | ||||||
|     t.Set(jwt.SubjectKey, user.UUID.String())   // User ID |     t.Set(jwt.SubjectKey, user.ID)   // User ID | ||||||
|     t.Set(jwt.AudienceKey, `imagini`)     // App ID |     t.Set(jwt.AudienceKey, `imagini`)     // App ID | ||||||
|     t.Set(jwt.IssuedAtKey, tm)            // Issued At |     t.Set(jwt.IssuedAtKey, tm)            // Issued At | ||||||
| 
 | 
 | ||||||
| @ -146,13 +154,13 @@ func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.D | |||||||
|     return string(signed), nil |     return string(signed), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) CreateJWTAccessToken(user models.User, device models.Device) (string, error) { | func (auth *AuthManager) CreateJWTAccessToken(user graphql.User, device graphql.Device) (string, error) { | ||||||
|     // Create New Token |     // Create New Token | ||||||
|     tm := time.Now() |     tm := time.Now() | ||||||
|     t := jwt.New() |     t := jwt.New() | ||||||
|     t.Set(`did`, device.UUID.String())                  // Device ID |     t.Set(`did`, device.ID)                    // Device ID | ||||||
|     t.Set(`role`, auth.getRole(user))               // User Role (Admin / User) |     t.Set(`role`, auth.getRole(user))               // User Role (Admin / User) | ||||||
|     t.Set(jwt.SubjectKey, user.UUID.String())           // User ID |     t.Set(jwt.SubjectKey, user.ID)             // User ID | ||||||
|     t.Set(jwt.AudienceKey, `imagini`)               // App ID |     t.Set(jwt.AudienceKey, `imagini`)               // App ID | ||||||
|     t.Set(jwt.IssuedAtKey, tm)                      // Issued At |     t.Set(jwt.IssuedAtKey, tm)                      // Issued At | ||||||
|     t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 2)) // 2 Hour Access Key |     t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 2)) // 2 Hour Access Key | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| package auth | package auth | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func authenticateLDAPUser(user models.User, pw string) bool { | func authenticateLDAPUser(user model.User, pw string) bool { | ||||||
|     return false |     return false | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,12 +3,12 @@ package auth | |||||||
| import ( | import ( | ||||||
|     "golang.org/x/crypto/bcrypt" |     "golang.org/x/crypto/bcrypt" | ||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func authenticateLocalUser(user models.User, pw string) bool { | func authenticateLocalUser(user model.User, pw string) bool { | ||||||
|     bPassword :=[]byte(pw) |     bPassword :=[]byte(pw) | ||||||
|     err := bcrypt.CompareHashAndPassword([]byte(user.Password), bPassword) |     err := bcrypt.CompareHashAndPassword([]byte(*user.Password), bPassword) | ||||||
|     if err == nil { |     if err == nil { | ||||||
|         log.Info("[auth] Authentication successfull: ", user.Username) |         log.Info("[auth] Authentication successfull: ", user.Username) | ||||||
|         return true |         return true | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ import ( | |||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/config" |     "reichard.io/imagini/internal/config" | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type DBManager struct { | type DBManager struct { | ||||||
| @ -35,16 +35,15 @@ func NewMgr(c *config.Config) *DBManager { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Initialize database |     // Initialize database | ||||||
|     dbm.db.AutoMigrate(&models.ServerSetting{}) |     dbm.db.AutoMigrate(&model.Device{}) | ||||||
|     dbm.db.AutoMigrate(&models.Device{}) |     dbm.db.AutoMigrate(&model.User{}) | ||||||
|     dbm.db.AutoMigrate(&models.User{}) |     dbm.db.AutoMigrate(&model.MediaItem{}) | ||||||
|     dbm.db.AutoMigrate(&models.MediaItem{}) |     dbm.db.AutoMigrate(&model.Tag{}) | ||||||
|     dbm.db.AutoMigrate(&models.Tag{}) |     dbm.db.AutoMigrate(&model.Album{}) | ||||||
|     dbm.db.AutoMigrate(&models.Album{}) |  | ||||||
| 
 | 
 | ||||||
|     // Determine whether to bootstrap |     // Determine whether to bootstrap | ||||||
|     var count int64 |     var count int64 | ||||||
|     dbm.db.Model(&models.User{}).Count(&count) |     dbm.db.Model(&model.User{}).Count(&count) | ||||||
|     if count == 0 { |     if count == 0 { | ||||||
|         dbm.bootstrapDatabase() |         dbm.bootstrapDatabase() | ||||||
|     } |     } | ||||||
| @ -54,17 +53,23 @@ func NewMgr(c *config.Config) *DBManager { | |||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) bootstrapDatabase() { | func (dbm *DBManager) bootstrapDatabase() { | ||||||
|     log.Info("[query] Bootstrapping database.") |     log.Info("[query] Bootstrapping database.") | ||||||
|     err := dbm.CreateUser(&models.User{ | 
 | ||||||
|  |     password := "admin" | ||||||
|  |     user := &model.User{ | ||||||
|         Username: "admin", |         Username: "admin", | ||||||
|         Password: "admin", |  | ||||||
|         AuthType: "Local", |         AuthType: "Local", | ||||||
|     }) |         Password: &password, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     err := dbm.CreateUser(user) | ||||||
| 
 | 
 | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         log.Fatal("[query] Unable to bootstrap database.") |         log.Fatal("[query] Unable to bootstrap database.") | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // func (dmb *DBManager) {} | ||||||
|  | 
 | ||||||
| func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, error) { | func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, error) { | ||||||
|     // TODO: |     // TODO: | ||||||
|     //  - Where Filters |     //  - Where Filters | ||||||
| @ -72,7 +77,7 @@ func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, erro | |||||||
|     //  - Paging Filters |     //  - Paging Filters | ||||||
| 
 | 
 | ||||||
|     objType := fmt.Sprintf("%T", dest) |     objType := fmt.Sprintf("%T", dest) | ||||||
|     if objType == "*[]models.MediaItem" { |     if objType == "*[]model.MediaItem" { | ||||||
|         // TODO: Validate MediaItem Type |         // TODO: Validate MediaItem Type | ||||||
|     } else { |     } else { | ||||||
|         // Return Error |         // Return Error | ||||||
|  | |||||||
| @ -4,27 +4,28 @@ import ( | |||||||
|     "github.com/google/uuid" |     "github.com/google/uuid" | ||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateDevice (device *models.Device) error { | func (dbm *DBManager) CreateDevice (device *model.Device) error { | ||||||
|     log.Info("[db] Creating device: ", device.Name) |     log.Info("[db] Creating device: ", device.Name) | ||||||
|     device.RefreshKey = uuid.New().String() |     refreshKey := uuid.New().String() | ||||||
|  |     device.RefreshKey = &refreshKey | ||||||
|     err := dbm.db.Create(&device).Error |     err := dbm.db.Create(&device).Error | ||||||
|     return err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) Device (device *models.Device) (models.Device, error) { | func (dbm *DBManager) Device (device *model.Device) (model.Device, error) { | ||||||
|     var foundDevice models.Device |     var foundDevice model.Device | ||||||
|     var count int64 |     var count int64 | ||||||
|     err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error |     err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error | ||||||
|     return foundDevice, err |     return foundDevice, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) DeleteDevice (user *models.Device) error { | func (dbm *DBManager) DeleteDevice (user *model.Device) error { | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) UpdateRefreshToken (device *models.Device, refreshToken string) error { | func (dbm *DBManager) UpdateRefreshToken (device *model.Device, refreshToken string) error { | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,17 +3,17 @@ package db | |||||||
| import ( | import ( | ||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateMediaItem (mediaItem *models.MediaItem) error { | func (dbm *DBManager) CreateMediaItem (mediaItem *model.MediaItem) error { | ||||||
|     log.Info("[db] Creating media item: ", mediaItem.FileName) |     log.Info("[db] Creating media item: ", mediaItem.FileName) | ||||||
|     err := dbm.db.Create(&mediaItem).Error |     err := dbm.db.Create(&mediaItem).Error | ||||||
|     return err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) MediaItems(mediaItemFilter *models.MediaItem) ([]models.MediaItem, int64, error) { | func (dbm *DBManager) MediaItems(mediaItemFilter *model.MediaItem) ([]model.MediaItem, int64, error) { | ||||||
|     var mediaItems []models.MediaItem |     var mediaItems []model.MediaItem | ||||||
|     var count int64 |     var count int64 | ||||||
| 
 | 
 | ||||||
|     err := dbm.db.Where(&mediaItemFilter).Find(&mediaItems).Count(&count).Error; |     err := dbm.db.Where(&mediaItemFilter).Find(&mediaItems).Count(&count).Error; | ||||||
|  | |||||||
| @ -4,32 +4,33 @@ import ( | |||||||
|     "golang.org/x/crypto/bcrypt" |     "golang.org/x/crypto/bcrypt" | ||||||
|     log "github.com/sirupsen/logrus" |     log "github.com/sirupsen/logrus" | ||||||
| 
 | 
 | ||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/graph/model" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateUser(user *models.User) error { | func (dbm *DBManager) CreateUser(user *model.User) error { | ||||||
|     log.Info("[db] Creating user: ", user.Username) |     log.Info("[db] Creating user: ", user.Username) | ||||||
|     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) |     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*user.Password), bcrypt.DefaultCost) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         log.Error(err) |         log.Error(err) | ||||||
|         return err |         return err | ||||||
|     } |     } | ||||||
|     user.Password = string(hashedPassword) |     stringHashedPassword := string(hashedPassword) | ||||||
|  |     user.Password = &stringHashedPassword | ||||||
|     err = dbm.db.Create(&user).Error |     err = dbm.db.Create(&user).Error | ||||||
|     return err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) User (user *models.User) (models.User, error) { | func (dbm *DBManager) User (user *model.User) (model.User, error) { | ||||||
|     var foundUser models.User |     var foundUser model.User | ||||||
|     var count int64 |     var count int64 | ||||||
|     err := dbm.db.Where(&user).First(&foundUser).Count(&count).Error |     err := dbm.db.Where(&user).First(&foundUser).Count(&count).Error | ||||||
|     return foundUser, err |     return foundUser, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) DeleteUser (user models.User) error { | func (dbm *DBManager) DeleteUser (user model.User) error { | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) UpdatePassword (user models.User, pw string) { | func (dbm *DBManager) UpdatePassword (user model.User, pw string) { | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,96 +0,0 @@ | |||||||
| package models |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "time" |  | ||||||
|     "strings" |  | ||||||
|     "reflect" |  | ||||||
|     "gorm.io/gorm" |  | ||||||
|     "github.com/google/uuid" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Base struct { |  | ||||||
|     UUID      uuid.UUID         `json:"uuid"        gorm:"type:uuid;primarykey"` |  | ||||||
|     CreatedAt time.Time         `json:"created_at"` |  | ||||||
|     UpdatedAt time.Time         `json:"updated_at"` |  | ||||||
|     DeletedAt gorm.DeletedAt    `json:"-"           gorm:"index"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (base *Base) BeforeCreate(tx *gorm.DB) (err error) { |  | ||||||
|     base.UUID = uuid.New() |  | ||||||
|     return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ServerSetting struct { |  | ||||||
|     Base |  | ||||||
|     Name        string  `json:"name"        gorm:"not null"` |  | ||||||
|     Description string  `json:"description" gorm:"not null"` |  | ||||||
|     Value       string  `json:"value"       gorm:"not null"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Device struct { |  | ||||||
|     Base |  | ||||||
|     UserUUID     uuid.UUID `json:"-"      gorm:"not null"` |  | ||||||
|     User         User      `json:"user"   gorm:"ForeignKey:UUID;References:UserUUID;not null"` // User |  | ||||||
|     Name         string    `json:"name"   gorm:"not null"`                                     // Name of Device |  | ||||||
|     Type         string    `json:"type"   gorm:"not null"`                                     // Android, iOS, Chrome, FireFox, Edge |  | ||||||
|     RefreshKey   string    `json:"-"`                                                          // Device Specific Refresh Key |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type User struct { |  | ||||||
|     Base |  | ||||||
|     Email           string  `json:"email"       gorm:"unique"`                 // Email |  | ||||||
|     Username        string  `json:"username"    gorm:"unique"`                 // Username |  | ||||||
|     FirstName       string  `json:"first_name"`                                // First Name |  | ||||||
|     LastName        string  `json:"last_name"`                                 // Last Name |  | ||||||
|     Role            string  `json:"role"`                                      // Role |  | ||||||
|     AuthType        string  `json:"auth_type"   gorm:"default:Local;not null"` // Auth Type (E.g. Local, LDAP) |  | ||||||
|     Password        string  `json:"-"`                                         // Hased & Salted Password |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type MediaItem struct { |  | ||||||
|     Base |  | ||||||
|     UserUUID    uuid.UUID   `json:"-"           gorm:"not null"` |  | ||||||
|     User        User        `json:"-"           gorm:"ForeignKey:UUID;References:UserUUID;not null"` // User |  | ||||||
|     EXIFDate    time.Time   `json:"exif_date"`                                                       // EXIF Date |  | ||||||
|     Latitude    *float32    `json:"latitude"    gorm:"type:decimal(10,2)"`                           // Decimal Latitude |  | ||||||
|     Longitude   *float32    `json:"longitude"   gorm:"type:decimal(10,2)"`                           // Decimal Longitude |  | ||||||
|     MediaType   string      `json:"media_type"  gorm:"default:Image;not null"`                       // Image, Video |  | ||||||
|     OrigName    string      `json:"orig_name"   gorm:"not null"`                                     // Original Name |  | ||||||
|     FileName    string      `json:"file_name"   gorm:"not null"`                                     // File Name |  | ||||||
|     Tags        []Tag       `json:"tags"        gorm:"many2many:media_tags;"`                        // Associated Tag UUIDs |  | ||||||
|     Albums      []Album     `json:"albums"      gorm:"many2many:media_albums;"`                      // Associated Album UUIDs |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Tag struct { |  | ||||||
|     Base |  | ||||||
|     Name        string  `json:"name"    gorm:"not null"` // Tag Name |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Album struct { |  | ||||||
|     Base |  | ||||||
|     Name        string  `json:"name"    gorm:"not null"` // Album Name |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func JSONFields(model interface{}) map[string]struct{} { |  | ||||||
|     jsonFields := make(map[string]struct{}) |  | ||||||
|     val := reflect.ValueOf(model) |  | ||||||
|     t := val.Type() |  | ||||||
|     for i := 0; i < t.NumField(); i++ { |  | ||||||
|         jsonField := strings.TrimSpace(t.Field(i).Tag.Get("json")) |  | ||||||
| 
 |  | ||||||
|         if jsonField == "" { |  | ||||||
|             continue |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         jsonSplit := strings.Split(jsonField, ",") |  | ||||||
|         fieldVal := strings.TrimSpace(jsonSplit[0]) |  | ||||||
| 
 |  | ||||||
|         if fieldVal == "" || fieldVal == "-" { |  | ||||||
|             continue |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         jsonFields[fieldVal] = struct{}{} |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return jsonFields |  | ||||||
| } |  | ||||||
							
								
								
									
										240
									
								
								plugin/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								plugin/models.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,240 @@ | |||||||
|  | package plugin | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/types" | ||||||
|  | 	"sort" | ||||||
|  | 
 | ||||||
|  | 	"github.com/99designs/gqlgen/codegen/config" | ||||||
|  | 	"github.com/99designs/gqlgen/codegen/templates" | ||||||
|  | 	"github.com/99designs/gqlgen/plugin" | ||||||
|  | 	"github.com/vektah/gqlparser/v2/ast" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type BuildMutateHook = func(b *ModelBuild) *ModelBuild | ||||||
|  | 
 | ||||||
|  | func defaultBuildMutateHook(b *ModelBuild) *ModelBuild { | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ModelBuild struct { | ||||||
|  | 	PackageName string | ||||||
|  | 	Interfaces  []*Interface | ||||||
|  | 	Models      []*Object | ||||||
|  | 	Enums       []*Enum | ||||||
|  | 	Scalars     []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Interface struct { | ||||||
|  | 	Description string | ||||||
|  | 	Name        string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Object struct { | ||||||
|  | 	Description string | ||||||
|  | 	Name        string | ||||||
|  | 	Fields      []*Field | ||||||
|  | 	Implements  []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Field struct { | ||||||
|  | 	Description string | ||||||
|  | 	Name        string | ||||||
|  | 	Type        types.Type | ||||||
|  | 	Tag         string | ||||||
|  |     Gorm        string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Enum struct { | ||||||
|  | 	Description string | ||||||
|  | 	Name        string | ||||||
|  | 	Values      []*EnumValue | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type EnumValue struct { | ||||||
|  | 	Description string | ||||||
|  | 	Name        string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func New() plugin.Plugin { | ||||||
|  | 	return &Plugin{ | ||||||
|  | 		MutateHook: defaultBuildMutateHook, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Plugin struct { | ||||||
|  | 	MutateHook BuildMutateHook | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ plugin.ConfigMutator = &Plugin{} | ||||||
|  | 
 | ||||||
|  | func (m *Plugin) Name() string { | ||||||
|  | 	return "imaginimodel" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *Plugin) MutateConfig(cfg *config.Config) error { | ||||||
|  | 	binder := cfg.NewBinder() | ||||||
|  | 
 | ||||||
|  | 	b := &ModelBuild{ | ||||||
|  | 		PackageName: cfg.Model.Package, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, schemaType := range cfg.Schema.Types { | ||||||
|  | 		if schemaType.BuiltIn { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		switch schemaType.Kind { | ||||||
|  | 		case ast.Interface, ast.Union: | ||||||
|  | 			it := &Interface{ | ||||||
|  | 				Description: schemaType.Description, | ||||||
|  | 				Name:        schemaType.Name, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			b.Interfaces = append(b.Interfaces, it) | ||||||
|  | 		case ast.Object, ast.InputObject: | ||||||
|  | 			if schemaType == cfg.Schema.Query || schemaType == cfg.Schema.Mutation || schemaType == cfg.Schema.Subscription { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			it := &Object{ | ||||||
|  | 				Description: schemaType.Description, | ||||||
|  | 				Name:        schemaType.Name, | ||||||
|  | 			} | ||||||
|  | 			for _, implementor := range cfg.Schema.GetImplements(schemaType) { | ||||||
|  | 				it.Implements = append(it.Implements, implementor.Name) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for _, field := range schemaType.Fields { | ||||||
|  | 				var typ types.Type | ||||||
|  | 				fieldDef := cfg.Schema.Types[field.Type.Name()] | ||||||
|  | 
 | ||||||
|  | 				if cfg.Models.UserDefined(field.Type.Name()) { | ||||||
|  | 					var err error | ||||||
|  | 					typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0]) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					switch fieldDef.Kind { | ||||||
|  | 					case ast.Scalar: | ||||||
|  | 						// no user defined model, referencing a default scalar | ||||||
|  | 						typ = types.NewNamed( | ||||||
|  | 							types.NewTypeName(0, cfg.Model.Pkg(), "string", nil), | ||||||
|  | 							nil, | ||||||
|  | 							nil, | ||||||
|  | 						) | ||||||
|  | 
 | ||||||
|  | 					case ast.Interface, ast.Union: | ||||||
|  | 						// no user defined model, referencing a generated interface type | ||||||
|  | 						typ = types.NewNamed( | ||||||
|  | 							types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), | ||||||
|  | 							types.NewInterfaceType([]*types.Func{}, []types.Type{}), | ||||||
|  | 							nil, | ||||||
|  | 						) | ||||||
|  | 
 | ||||||
|  | 					case ast.Enum: | ||||||
|  | 						// no user defined model, must reference a generated enum | ||||||
|  | 						typ = types.NewNamed( | ||||||
|  | 							types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), | ||||||
|  | 							nil, | ||||||
|  | 							nil, | ||||||
|  | 						) | ||||||
|  | 
 | ||||||
|  | 					case ast.Object, ast.InputObject: | ||||||
|  | 						// no user defined model, must reference a generated struct | ||||||
|  | 						typ = types.NewNamed( | ||||||
|  | 							types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), | ||||||
|  | 							types.NewStruct(nil, nil), | ||||||
|  | 							nil, | ||||||
|  | 						) | ||||||
|  | 
 | ||||||
|  | 					default: | ||||||
|  | 						panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind)) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				name := field.Name | ||||||
|  | 				if nameOveride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOveride != "" { | ||||||
|  | 					name = nameOveride | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				typ = binder.CopyModifiersFromAst(field.Type, typ) | ||||||
|  | 
 | ||||||
|  | 				if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { | ||||||
|  | 					typ = types.NewPointer(typ) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  |                 gormType := "" | ||||||
|  |                 directive := field.Directives.ForName("meta") | ||||||
|  |                 if directive != nil { | ||||||
|  |                     arg := directive.Arguments.ForName("gorm") | ||||||
|  |                     if arg != nil { | ||||||
|  |                         gormType = fmt.Sprintf("gorm:\"%s\"", arg.Value.Raw) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  | 				it.Fields = append(it.Fields, &Field{ | ||||||
|  | 					Name:        name, | ||||||
|  | 					Type:        typ, | ||||||
|  | 					Description: field.Description, | ||||||
|  | 					Tag:         `json:"` + field.Name + `"`, | ||||||
|  |                     Gorm:        gormType, | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			b.Models = append(b.Models, it) | ||||||
|  | 		case ast.Enum: | ||||||
|  | 			it := &Enum{ | ||||||
|  | 				Name:        schemaType.Name, | ||||||
|  | 				Description: schemaType.Description, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for _, v := range schemaType.EnumValues { | ||||||
|  | 				it.Values = append(it.Values, &EnumValue{ | ||||||
|  | 					Name:        v.Name, | ||||||
|  | 					Description: v.Description, | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			b.Enums = append(b.Enums, it) | ||||||
|  | 		case ast.Scalar: | ||||||
|  | 			b.Scalars = append(b.Scalars, schemaType.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sort.Slice(b.Enums, func(i, j int) bool { return b.Enums[i].Name < b.Enums[j].Name }) | ||||||
|  | 	sort.Slice(b.Models, func(i, j int) bool { return b.Models[i].Name < b.Models[j].Name }) | ||||||
|  | 	sort.Slice(b.Interfaces, func(i, j int) bool { return b.Interfaces[i].Name < b.Interfaces[j].Name }) | ||||||
|  | 
 | ||||||
|  | 	for _, it := range b.Enums { | ||||||
|  | 		cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) | ||||||
|  | 	} | ||||||
|  | 	for _, it := range b.Models { | ||||||
|  | 		cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) | ||||||
|  | 	} | ||||||
|  | 	for _, it := range b.Interfaces { | ||||||
|  | 		cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name)) | ||||||
|  | 	} | ||||||
|  | 	for _, it := range b.Scalars { | ||||||
|  | 		cfg.Models.Add(it, "github.com/99designs/gqlgen/graphql.String") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(b.Models) == 0 && len(b.Enums) == 0 && len(b.Interfaces) == 0 && len(b.Scalars) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if m.MutateHook != nil { | ||||||
|  | 		b = m.MutateHook(b) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return templates.Render(templates.Options{ | ||||||
|  | 		PackageName:     cfg.Model.Package, | ||||||
|  | 		Filename:        cfg.Model.Filename, | ||||||
|  | 		Data:            b, | ||||||
|  | 		GeneratedHeader: true, | ||||||
|  | 		Packages:        cfg.Packages, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isStruct(t types.Type) bool { | ||||||
|  | 	_, is := t.Underlying().(*types.Struct) | ||||||
|  | 	return is | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								plugin/models.gotpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								plugin/models.gotpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | {{ reserveImport "context"  }} | ||||||
|  | {{ reserveImport "fmt"  }} | ||||||
|  | {{ reserveImport "io"  }} | ||||||
|  | {{ reserveImport "strconv"  }} | ||||||
|  | {{ reserveImport "time"  }} | ||||||
|  | {{ reserveImport "sync"  }} | ||||||
|  | {{ reserveImport "errors"  }} | ||||||
|  | {{ reserveImport "bytes"  }} | ||||||
|  | 
 | ||||||
|  | {{ reserveImport "github.com/vektah/gqlparser/v2" }} | ||||||
|  | {{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} | ||||||
|  | {{ reserveImport "github.com/99designs/gqlgen/graphql" }} | ||||||
|  | {{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} | ||||||
|  | 
 | ||||||
|  | {{- range $model := .Interfaces }} | ||||||
|  | 	{{ with .Description }} {{.|prefixLines "// "}} {{ end }} | ||||||
|  | 	type {{.Name|go }} interface { | ||||||
|  | 		Is{{.Name|go }}() | ||||||
|  | 	} | ||||||
|  | {{- end }} | ||||||
|  | 
 | ||||||
|  | {{ range $model := .Models }} | ||||||
|  | 	{{with .Description }} {{.|prefixLines "// "}} {{end}} | ||||||
|  | 	type {{ .Name|go }} struct { | ||||||
|  | 		{{- range $field := .Fields }} | ||||||
|  | 			{{- with .Description }} | ||||||
|  | 				{{.|prefixLines "// "}} | ||||||
|  | 			{{- end}} | ||||||
|  | 			{{ $field.Name|go }} {{$field.Type | ref}} `{{$field.Tag}} {{$field.Gorm}}` | ||||||
|  | 		{{- end }} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	{{- range $iface := .Implements }} | ||||||
|  | 		func ({{ $model.Name|go }}) Is{{ $iface|go }}() {} | ||||||
|  | 	{{- end }} | ||||||
|  | {{- end}} | ||||||
|  | 
 | ||||||
|  | {{ range $enum := .Enums }} | ||||||
|  | 	{{ with .Description }} {{.|prefixLines "// "}} {{end}} | ||||||
|  | 	type {{.Name|go }} string | ||||||
|  | 	const ( | ||||||
|  | 	{{- range $value := .Values}} | ||||||
|  | 		{{- with .Description}} | ||||||
|  | 			{{.|prefixLines "// "}} | ||||||
|  | 		{{- end}} | ||||||
|  | 		{{ $enum.Name|go }}{{ .Name|go }} {{$enum.Name|go }} = {{.Name|quote}} | ||||||
|  | 	{{- end }} | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var All{{.Name|go }} = []{{ .Name|go }}{ | ||||||
|  | 	{{- range $value := .Values}} | ||||||
|  | 		{{$enum.Name|go }}{{ .Name|go }}, | ||||||
|  | 	{{- end }} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	func (e {{.Name|go }}) IsValid() bool { | ||||||
|  | 		switch e { | ||||||
|  | 		case {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Name|go }}{{ $element.Name|go }}{{end}}: | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	func (e {{.Name|go }}) String() string { | ||||||
|  | 		return string(e) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	func (e *{{.Name|go }}) UnmarshalGQL(v interface{}) error { | ||||||
|  | 		str, ok := v.(string) | ||||||
|  | 		if !ok { | ||||||
|  | 			return fmt.Errorf("enums must be strings") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		*e = {{ .Name|go }}(str) | ||||||
|  | 		if !e.IsValid() { | ||||||
|  | 			return fmt.Errorf("%s is not a valid {{ .Name }}", str) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	func (e {{.Name|go }}) MarshalGQL(w io.Writer) { | ||||||
|  | 		fmt.Fprint(w, strconv.Quote(e.String())) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | {{- end }} | ||||||
		Reference in New Issue
	
	Block a user