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" | ||||
| 
 | ||||
|     "reichard.io/imagini/cmd/server" | ||||
| 
 | ||||
|     "reichard.io/imagini/plugin" | ||||
|     "github.com/99designs/gqlgen/api" | ||||
| 	"github.com/99designs/gqlgen/codegen/config" | ||||
| ) | ||||
| 
 | ||||
| type UTCFormatter struct { | ||||
| @ -21,7 +25,6 @@ func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) { | ||||
| func main() { | ||||
|     log.SetFormatter(UTCFormatter{&log.TextFormatter{FullTimestamp: true}}) | ||||
| 
 | ||||
|     log.Info("Starting Imagini") | ||||
|     app := &cli.App{ | ||||
|         Name: "Imagini", | ||||
|         Usage: "A self hosted photo library.", | ||||
| @ -32,6 +35,11 @@ func main() { | ||||
|                 Usage:   "Start Imagini web server.", | ||||
|                 Action:  cmdServer, | ||||
|             }, | ||||
|             { | ||||
| 				Name:  "generate", | ||||
| 				Usage: "generate graphql schema", | ||||
| 				Action: cmdGenerate, | ||||
| 			}, | ||||
|         }, | ||||
|     } | ||||
|     err := app.Run(os.Args) | ||||
| @ -41,6 +49,7 @@ func main() { | ||||
| } | ||||
| 
 | ||||
| func cmdServer(ctx *cli.Context) error { | ||||
|     log.Info("Starting Imagini Server") | ||||
|     server := server.NewServer() | ||||
|     server.StartServer() | ||||
| 
 | ||||
| @ -53,3 +62,24 @@ func cmdServer(ctx *cli.Context) error { | ||||
| 
 | ||||
|     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 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/99designs/gqlgen v0.13.0 | ||||
| 	github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect | ||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||
| 	github.com/disintegration/imaging v1.6.2 // indirect | ||||
| @ -14,6 +15,7 @@ require ( | ||||
| 	github.com/sirupsen/logrus v1.7.0 | ||||
| 	github.com/tus/tusd v1.4.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/image v0.0.0-20201208152932-35266b937fa6 // indirect | ||||
| 	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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= | ||||
| 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/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/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/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= | ||||
| @ -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 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/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/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= | ||||
| 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/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/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.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||
| 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/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= | ||||
| 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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | ||||
| 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-20181103185306-d547d1d9531e/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/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.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= | ||||
| 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/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/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/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/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= | ||||
| 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/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= | ||||
| 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.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= | ||||
| 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/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/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/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/pkg/errors v0.8.0/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/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/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/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/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/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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||||
| 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/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/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| 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/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/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/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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| 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-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/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/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= | ||||
| @ -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-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-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-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-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-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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| 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-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-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-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-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-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-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/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-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||||
| 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.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/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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| 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/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.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.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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | ||||
| 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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| 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" | ||||
| 
 | ||||
|     "reichard.io/imagini/internal/models" | ||||
|     graphql "reichard.io/imagini/graph/model" | ||||
| ) | ||||
| 
 | ||||
| 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. | ||||
|  **/ | ||||
| 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.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) | ||||
|         createdDevice, err := api.DB.Device(&requestedDevice) | ||||
|         return createdDevice, err | ||||
|     } | ||||
| 
 | ||||
|     foundDevice, err := api.DB.Device(&models.Device{ | ||||
|         Base: models.Base{ UUID: requestedDevice.UUID }, | ||||
|         User: user, | ||||
|     foundDevice, err := api.DB.Device(&graphql.Device{ | ||||
|         ID: requestedDevice.ID, | ||||
|         User: &user, | ||||
|     }) | ||||
| 
 | ||||
|     return foundDevice, err | ||||
| } | ||||
| 
 | ||||
| func deriveDeviceType(r *http.Request) string { | ||||
| func deriveDeviceType(r *http.Request) graphql.DeviceType { | ||||
|     userAgent := strings.ToLower(r.Header.Get("User-Agent")) | ||||
|     if strings.HasPrefix(userAgent, "ios-imagini"){ | ||||
|         return "iOS" | ||||
|         return graphql.DeviceTypeIOs | ||||
|     } else if strings.HasPrefix(userAgent, "android-imagini"){ | ||||
|         return "Android" | ||||
|         return graphql.DeviceTypeAndroid | ||||
|     } else if strings.HasPrefix(userAgent, "chrome"){ | ||||
|         return "Chrome" | ||||
|         return graphql.DeviceTypeChrome | ||||
|     } else if strings.HasPrefix(userAgent, "firefox"){ | ||||
|         return "Firefox" | ||||
|         return graphql.DeviceTypeFirefox | ||||
|     } else if strings.HasPrefix(userAgent, "msie"){ | ||||
|         return "Internet Explorer" | ||||
|         return graphql.DeviceTypeInternetExplorer | ||||
|     } else if strings.HasPrefix(userAgent, "edge"){ | ||||
|         return "Edge" | ||||
|         return graphql.DeviceTypeEdge | ||||
|     } else if strings.HasPrefix(userAgent, "safari"){ | ||||
|         return "Safari" | ||||
|         return graphql.DeviceTypeSafari | ||||
|     } | ||||
|     return "Unknown" | ||||
|     return graphql.DeviceTypeUnknown | ||||
| } | ||||
| 
 | ||||
| func deriveRequestedDevice(r *http.Request) models.Device { | ||||
|     deviceSkeleton := models.Device{} | ||||
| func deriveRequestedDevice(r *http.Request) graphql.Device { | ||||
|     deviceSkeleton := graphql.Device{} | ||||
|     authHeader := r.Header.Get("X-Imagini-Authorization") | ||||
|     splitAuthInfo := strings.Split(authHeader, ",") | ||||
| 
 | ||||
| @ -137,19 +138,20 @@ func deriveRequestedDevice(r *http.Request) models.Device { | ||||
| 
 | ||||
|         // Derive Key | ||||
|         key := strings.ToLower(strings.TrimSpace(splitItem[0])) | ||||
|         if key != "deviceuuid" && key != "devicename" { | ||||
|         if key != "deviceid" && key != "devicename" { | ||||
|             continue | ||||
|         } | ||||
| 
 | ||||
|         // Derive Value | ||||
|         val := trimQuotes(strings.TrimSpace(splitItem[1])) | ||||
|         if key == "deviceuuid" { | ||||
|         if key == "deviceid" { | ||||
|             parsedDeviceUUID, err := uuid.Parse(val) | ||||
|             if err != nil { | ||||
|                 log.Warn("[auth] deriveRequestedDevice - Unable to parse requested DeviceUUID: ", val) | ||||
|                 continue | ||||
|             } | ||||
|             deviceSkeleton.Base = models.Base{UUID: parsedDeviceUUID} | ||||
|             stringDeviceUUID := parsedDeviceUUID.String() | ||||
|             deviceSkeleton.ID = &stringDeviceUUID | ||||
|         } else if key == "devicename" { | ||||
|             deviceSkeleton.Name = val | ||||
|         } | ||||
| @ -157,7 +159,7 @@ func deriveRequestedDevice(r *http.Request) models.Device { | ||||
| 
 | ||||
|     // If name not set, set to type | ||||
|     if deviceSkeleton.Name == "" { | ||||
|         deviceSkeleton.Name = deviceSkeleton.Type | ||||
|         deviceSkeleton.Name = deviceSkeleton.Type.String() | ||||
|     } | ||||
| 
 | ||||
|     return deviceSkeleton | ||||
| @ -196,9 +198,12 @@ func (api *API) refreshAccessToken(w http.ResponseWriter, r *http.Request) (jwt. | ||||
|         return nil, err | ||||
|     } | ||||
| 
 | ||||
|     stringUserUUID := userUUID.String() | ||||
|     stringDeviceUUID := deviceUUID.String() | ||||
| 
 | ||||
|     // Device & User Skeleton | ||||
|     user := models.User{Base: models.Base{UUID: userUUID}} | ||||
|     device := models.Device{Base: models.Base{UUID: deviceUUID}} | ||||
|     user := graphql.User{ID: &stringUserUUID} | ||||
|     device := graphql.Device{ID: &stringDeviceUUID} | ||||
| 
 | ||||
|     // Update token | ||||
|     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" | ||||
| 
 | ||||
|     "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() { | ||||
| 	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.mediaHandler, | ||||
|         api.authMiddleware, | ||||
|     )) | ||||
|     api.Router.HandleFunc("/api/v1/MediaItems", multipleMiddleware( | ||||
|         api.mediaItemsHandler, | ||||
|         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) | ||||
|     api.Router.HandleFunc("/logout", api.logoutHandler) | ||||
|     api.Router.HandleFunc("/login",  api.loginHandler) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // https://stackoverflow.com/a/59764037 | ||||
| func errorJSON(w http.ResponseWriter, err string, code int) { | ||||
|     errStruct := &models.APIResponse{Error: &models.APIError{Message: err, Code: int64(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/config" | ||||
| 
 | ||||
|     graphql "reichard.io/imagini/graph/model" | ||||
|     "reichard.io/imagini/internal/models" | ||||
|     "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) { | ||||
|     // By Username | ||||
|     foundUser, err := auth.DB.User(&models.User{Username: creds.User}) | ||||
| func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, graphql.User) { | ||||
|     // Search Objects | ||||
|     userByName := &graphql.User{} | ||||
|     userByName.Username = creds.User | ||||
| 
 | ||||
|     foundUser, err := auth.DB.User(userByName) | ||||
|     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 | ||||
| @ -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 | ||||
|     return "User" | ||||
| } | ||||
| @ -80,7 +87,8 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, | ||||
|     if err != nil { | ||||
|         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 { | ||||
|         return nil, err | ||||
|     } | ||||
| @ -88,7 +96,7 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, | ||||
|     // Verify & Validate Token | ||||
|     verifiedToken, err := jwt.ParseBytes(byteRefreshJWT, | ||||
|         jwt.WithValidate(true), | ||||
|         jwt.WithVerify(jwa.HS256, []byte(device.RefreshKey)), | ||||
|         jwt.WithVerify(jwa.HS256, []byte(*device.RefreshKey)), | ||||
|     ) | ||||
|     if err != nil { | ||||
|         fmt.Println("failed to parse payload: ", err) | ||||
| @ -111,17 +119,17 @@ func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, er | ||||
|     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 | ||||
|     byteKey := []byte(device.RefreshKey) | ||||
|     byteKey := []byte(*device.RefreshKey) | ||||
| 
 | ||||
|     // Create New Token | ||||
|     tm := time.Now() | ||||
|     t := jwt.New() | ||||
|     t.Set(`did`, device.UUID.String())          // Device ID | ||||
|     t.Set(jwt.SubjectKey, user.UUID.String())   // User ID | ||||
|     t.Set(jwt.AudienceKey, `imagini`)           // App ID | ||||
|     t.Set(jwt.IssuedAtKey, tm)                  // Issued At | ||||
|     t.Set(`did`, device.ID)          // Device ID | ||||
|     t.Set(jwt.SubjectKey, user.ID)   // User ID | ||||
|     t.Set(jwt.AudienceKey, `imagini`)     // App ID | ||||
|     t.Set(jwt.IssuedAtKey, tm)            // Issued At | ||||
| 
 | ||||
|     // iOS & Android = Never Expiring Refresh Token | ||||
|     if device.Type != "iOS" && device.Type != "Android" { | ||||
| @ -146,16 +154,16 @@ func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.D | ||||
|     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 | ||||
|     tm := time.Now() | ||||
|     t := jwt.New() | ||||
|     t.Set(`did`, device.UUID.String())                  // Device ID | ||||
|     t.Set(`role`, auth.getRole(user))                   // User Role (Admin / User) | ||||
|     t.Set(jwt.SubjectKey, user.UUID.String())           // User ID | ||||
|     t.Set(jwt.AudienceKey, `imagini`)                   // App ID | ||||
|     t.Set(jwt.IssuedAtKey, tm)                          // Issued At | ||||
|     t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 2))     // 2 Hour Access Key | ||||
|     t.Set(`did`, device.ID)                    // Device ID | ||||
|     t.Set(`role`, auth.getRole(user))               // User Role (Admin / User) | ||||
|     t.Set(jwt.SubjectKey, user.ID)             // User ID | ||||
|     t.Set(jwt.AudienceKey, `imagini`)               // App ID | ||||
|     t.Set(jwt.IssuedAtKey, tm)                      // Issued At | ||||
|     t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 2)) // 2 Hour Access Key | ||||
| 
 | ||||
|     // Validate Token Creation | ||||
|     _, err := json.MarshalIndent(t, "", "  ") | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| package auth | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
|  | ||||
| @ -3,12 +3,12 @@ package auth | ||||
| import ( | ||||
|     "golang.org/x/crypto/bcrypt" | ||||
|     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) | ||||
|     err := bcrypt.CompareHashAndPassword([]byte(user.Password), bPassword) | ||||
|     err := bcrypt.CompareHashAndPassword([]byte(*user.Password), bPassword) | ||||
|     if err == nil { | ||||
|         log.Info("[auth] Authentication successfull: ", user.Username) | ||||
|         return true | ||||
|  | ||||
| @ -11,7 +11,7 @@ import ( | ||||
|     log "github.com/sirupsen/logrus" | ||||
| 
 | ||||
|     "reichard.io/imagini/internal/config" | ||||
|     "reichard.io/imagini/internal/models" | ||||
|     "reichard.io/imagini/graph/model" | ||||
| ) | ||||
| 
 | ||||
| type DBManager struct { | ||||
| @ -35,16 +35,15 @@ func NewMgr(c *config.Config) *DBManager { | ||||
|     } | ||||
| 
 | ||||
|     // Initialize database | ||||
|     dbm.db.AutoMigrate(&models.ServerSetting{}) | ||||
|     dbm.db.AutoMigrate(&models.Device{}) | ||||
|     dbm.db.AutoMigrate(&models.User{}) | ||||
|     dbm.db.AutoMigrate(&models.MediaItem{}) | ||||
|     dbm.db.AutoMigrate(&models.Tag{}) | ||||
|     dbm.db.AutoMigrate(&models.Album{}) | ||||
|     dbm.db.AutoMigrate(&model.Device{}) | ||||
|     dbm.db.AutoMigrate(&model.User{}) | ||||
|     dbm.db.AutoMigrate(&model.MediaItem{}) | ||||
|     dbm.db.AutoMigrate(&model.Tag{}) | ||||
|     dbm.db.AutoMigrate(&model.Album{}) | ||||
| 
 | ||||
|     // Determine whether to bootstrap | ||||
|     var count int64 | ||||
|     dbm.db.Model(&models.User{}).Count(&count) | ||||
|     dbm.db.Model(&model.User{}).Count(&count) | ||||
|     if count == 0 { | ||||
|         dbm.bootstrapDatabase() | ||||
|     } | ||||
| @ -54,17 +53,23 @@ func NewMgr(c *config.Config) *DBManager { | ||||
| 
 | ||||
| func (dbm *DBManager) bootstrapDatabase() { | ||||
|     log.Info("[query] Bootstrapping database.") | ||||
|     err := dbm.CreateUser(&models.User{ | ||||
| 
 | ||||
|     password := "admin" | ||||
|     user := &model.User{ | ||||
|         Username: "admin", | ||||
|         Password: "admin", | ||||
|         AuthType: "Local", | ||||
|     }) | ||||
|         Password: &password, | ||||
|     } | ||||
| 
 | ||||
|     err := dbm.CreateUser(user) | ||||
| 
 | ||||
|     if err != nil { | ||||
|         log.Fatal("[query] Unable to bootstrap database.") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // func (dmb *DBManager) {} | ||||
| 
 | ||||
| func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, error) { | ||||
|     // TODO: | ||||
|     //  - Where Filters | ||||
| @ -72,7 +77,7 @@ func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, erro | ||||
|     //  - Paging Filters | ||||
| 
 | ||||
|     objType := fmt.Sprintf("%T", dest) | ||||
|     if objType == "*[]models.MediaItem" { | ||||
|     if objType == "*[]model.MediaItem" { | ||||
|         // TODO: Validate MediaItem Type | ||||
|     } else { | ||||
|         // Return Error | ||||
|  | ||||
| @ -4,27 +4,28 @@ import ( | ||||
|     "github.com/google/uuid" | ||||
|     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) | ||||
|     device.RefreshKey = uuid.New().String() | ||||
|     refreshKey := uuid.New().String() | ||||
|     device.RefreshKey = &refreshKey | ||||
|     err := dbm.db.Create(&device).Error | ||||
|     return err | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) Device (device *models.Device) (models.Device, error) { | ||||
|     var foundDevice models.Device | ||||
| func (dbm *DBManager) Device (device *model.Device) (model.Device, error) { | ||||
|     var foundDevice model.Device | ||||
|     var count int64 | ||||
|     err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error | ||||
|     return foundDevice, err | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) DeleteDevice (user *models.Device) error { | ||||
| func (dbm *DBManager) DeleteDevice (user *model.Device) error { | ||||
|     return nil | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) UpdateRefreshToken (device *models.Device, refreshToken string) error { | ||||
| func (dbm *DBManager) UpdateRefreshToken (device *model.Device, refreshToken string) error { | ||||
|     return nil | ||||
| } | ||||
|  | ||||
| @ -3,17 +3,17 @@ package db | ||||
| import ( | ||||
|     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) | ||||
|     err := dbm.db.Create(&mediaItem).Error | ||||
|     return err | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) MediaItems(mediaItemFilter *models.MediaItem) ([]models.MediaItem, int64, error) { | ||||
|     var mediaItems []models.MediaItem | ||||
| func (dbm *DBManager) MediaItems(mediaItemFilter *model.MediaItem) ([]model.MediaItem, int64, error) { | ||||
|     var mediaItems []model.MediaItem | ||||
|     var count int64 | ||||
| 
 | ||||
|     err := dbm.db.Where(&mediaItemFilter).Find(&mediaItems).Count(&count).Error; | ||||
|  | ||||
| @ -4,32 +4,33 @@ import ( | ||||
|     "golang.org/x/crypto/bcrypt" | ||||
|     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) | ||||
|     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) | ||||
|     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*user.Password), bcrypt.DefaultCost) | ||||
|     if err != nil { | ||||
|         log.Error(err) | ||||
|         return err | ||||
|     } | ||||
|     user.Password = string(hashedPassword) | ||||
|     stringHashedPassword := string(hashedPassword) | ||||
|     user.Password = &stringHashedPassword | ||||
|     err = dbm.db.Create(&user).Error | ||||
|     return err | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) User (user *models.User) (models.User, error) { | ||||
|     var foundUser models.User | ||||
| func (dbm *DBManager) User (user *model.User) (model.User, error) { | ||||
|     var foundUser model.User | ||||
|     var count int64 | ||||
|     err := dbm.db.Where(&user).First(&foundUser).Count(&count).Error | ||||
|     return foundUser, err | ||||
| } | ||||
| 
 | ||||
| func (dbm *DBManager) DeleteUser (user models.User) error { | ||||
| func (dbm *DBManager) DeleteUser (user model.User) error { | ||||
|     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