diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..dd0fdad --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,13 @@ +version: v1 +plugins: + - name : go + out: . + opt: paths=source_relative + - name: go-grpc + out: . + opt: paths=source_relative + - name: grpc-gateway + out: . + opt: paths=source_relative + - name: openapiv2 + out: . diff --git a/buf.lock b/buf.lock new file mode 100644 index 0000000..3435191 --- /dev/null +++ b/buf.lock @@ -0,0 +1,8 @@ +# Generated by buf. DO NOT EDIT. +version: v1 +deps: + - remote: buf.build + owner: googleapis + repository: googleapis + commit: 711e289f6a384c4caeebaff7c6931ade + digest: shake256:e08fb55dad7469f69df00304eed31427d2d1576e9aab31e6bf86642688e04caaf0372f15fe6974cf79432779a635b3ea401ca69c943976dc42749524e4c25d94 diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..69baf0f --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v1 +deps: + - buf.build/googleapis/googleapis +breaking: + use: + - FILE +lint: + use: + - DEFAULT diff --git a/cmd/server/main.go b/cmd/server/main.go index c393f44..487f711 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,14 +1,17 @@ package main import ( + "context" "flag" "fmt" "log" "net" + "net/http" "os" "git.chrishayward.xyz/x/users/proto" "git.chrishayward.xyz/x/users/server" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "gopkg.in/yaml.v3" @@ -20,8 +23,12 @@ import ( var configPath = flag.String("config", "./config/server.yaml", "--config=./config/server.yaml") type config struct { - Port int `yaml:"port"` - Secret string `yaml:"secret"` + Port int `yaml:"port"` + Secret string `yaml:"secret"` + Gateway struct { + URL string `yaml:"url"` + Port int `yaml:"port"` + } Database struct { Type string `yaml:"type"` File string `yaml:"file"` @@ -34,17 +41,28 @@ type config struct { } func main() { - // Parse the optional flags. + // Create the context. + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Parse the application flags. flag.Parse() // Read the config file. config := &config{} file, err := os.Open(*configPath) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to open config file: %v", err) } defer file.Close() if err := yaml.NewDecoder(file).Decode(&config); err != nil { + log.Fatalf("Failed to read config file: %v", err) + } + + // Create the server network listener. + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Port)) + if err != nil { log.Fatal(err) } @@ -65,16 +83,46 @@ func main() { db, _ = gorm.Open(sqlite.Open(config.Database.File), &gorm.Config{}) } - // Create the network listener. - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", config.Port)) + // Create the server. + grpcServer := grpc.NewServer() + proto.RegisterUsersServer(grpcServer, server.NewUsersServer(config.Secret, db)) + reflection.Register(grpcServer) + go grpcServer.Serve(lis) + + // Create the gateway client connection. + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", config.Port), grpc.WithInsecure()) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to create client connection: %v", err) } + defer conn.Close() + + // Create the gRPC gateway. + rmux := runtime.NewServeMux() + client := proto.NewUsersClient(conn) + err = proto.RegisterUserHandlerClient(ctx, rmux, client) + if err != nil { + log.Fatalf("Failed to register client handler: %v", err) + } + + // Create a standard HTTP router. + mux := http.NewServeMux() + + // Mount the gRPC gateway. + mux.Handle("/", rmux) + + // Create the gRPC OpenAPI UI. + mux.HandleFunc("/swagger-ui/swagger.json", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "./proto/users.swagger.json") + }) + mux.Handle("/swagger-ui/", + http.StripPrefix("/swagger-ui/", + http.FileServer(http.Dir("./modules/swagger-ui/dist")))) // Start listening for requests. - srv := grpc.NewServer() - proto.RegisterUsersServer(srv, server.NewUsersServer(config.Secret, db)) - reflection.Register(srv) - log.Printf("Listening on :%d", config.Port) - srv.Serve(lis) + log.Println(fmt.Sprintf("Listening on:\n=>\tgRPC[::]:%d\n=>\tHTTP[::]:%d", + config.Port, config.Gateway.Port)) + err = http.ListenAndServe(fmt.Sprintf(":%d", config.Gateway.Port), mux) + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } } diff --git a/flake.nix b/flake.nix index 6c9fd7e..3e17530 100644 --- a/flake.nix +++ b/flake.nix @@ -9,21 +9,15 @@ inputs.flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; myGo = "${pkgs.go}/bin/go"; - myProtoc = "${pkgs.grpc-tools}/bin/protoc"; - myUsersDir = "$MY_USERS_DIR"; - myUsersBuild = pkgs.writeShellScriptBin "users-build" '' - pushd ${myUsersDir} > /dev/null && - ${myProtoc} --proto_path=${myUsersDir} \ - --go_out=. --go_opt=paths=source_relative \ - --go-grpc_out=. --go-grpc_opt=paths=source_relative \ - proto/users.proto && - ${myGo} build -o ${myUsersDir}/bin/users-server ${myUsersDir}/cmd/server/main.go && - ${myGo} build -o ${myUsersDir}/bin/users-gateway ${myUsersDir}/cmd/gateway/main.go && - popd > /dev/null - ''; - myUsersTest = pkgs.writeShellScriptBin "users-test" '' - pushd ${myUsersDir} > /dev/null && - ${pkgs.parallel}/bin/parallel ::: ${myUsersDir}/bin/users-server ${myUsersDir}/bin/users-gateway + myBuf = "${pkgs.buf}/bin/buf"; + myDir = "$MY_USERS_DIR"; + myBuild = pkgs.writeShellScriptBin "users-build" '' + pushd ${myDir} > /dev/null && + sed -n 's,https://petstore.swagger.io/v2/swagger.json,swagger.json,g' \ + ${myDir}/modules/swagger-ui/dist/swagger-initializer.js && + ${myBuf} build && + ${myBuf} generate && + ${myGo} build -o ${myDir}/bin/users-server ${myDir}/cmd/server/main.go && popd > /dev/null ''; in @@ -35,13 +29,14 @@ protoc-gen-go protoc-gen-go-grpc + buf grpc grpcui grpcurl grpc-tools + grpc-gateway - myUsersBuild - myUsersTest + myBuild ]; shellHook = '' diff --git a/go.mod b/go.mod index 41b37f2..c8eaf1e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.20 require ( github.com/google/uuid v1.3.0 - golang.org/x/crypto v0.10.0 - google.golang.org/grpc v1.56.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 + golang.org/x/crypto v0.11.0 + google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.2 @@ -23,8 +24,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect ) diff --git a/go.sum b/go.sum index 8bcdcc9..e643d6e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -8,6 +9,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -18,7 +21,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= @@ -31,19 +34,22 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e h1:S83+ibolgyZ0bqz7KEsUOPErxcv4VzlszxY+31OfB/E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= diff --git a/proto/users.pb.go b/proto/users.pb.go index 9cf005c..092841d 100644 --- a/proto/users.pb.go +++ b/proto/users.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.29.1 -// protoc v3.15.6 +// protoc (unknown) // source: proto/users.proto package proto diff --git a/proto/users.swagger.json b/proto/users.swagger.json new file mode 100644 index 0000000..8f8ad60 --- /dev/null +++ b/proto/users.swagger.json @@ -0,0 +1,154 @@ +{ + "swagger": "2.0", + "info": { + "title": "proto/users.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "Users" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "usersAuthorizeResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/usersUserInfo" + }, + "roles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/usersUserRole" + } + } + } + }, + "usersChangePasswordResponse": { + "type": "object" + }, + "usersListRolesResponse": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/usersUserRole" + } + } + } + }, + "usersLoginResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/usersUserToken" + } + } + }, + "usersLogoutResponse": { + "type": "object" + }, + "usersRegisterResponse": { + "type": "object" + }, + "usersResetPasswordResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/usersUserToken" + } + } + }, + "usersSetRolesResponse": { + "type": "object" + }, + "usersUserForm": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "passwordAgain": { + "type": "string" + } + } + }, + "usersUserInfo": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64" + }, + "uuid": { + "type": "string" + } + } + }, + "usersUserRole": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "usersUserToken": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "expires": { + "type": "string", + "format": "int64" + } + } + } + } +} diff --git a/proto/users_grpc.pb.go b/proto/users_grpc.pb.go index 5cc4d4f..7d3964a 100644 --- a/proto/users_grpc.pb.go +++ b/proto/users_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v3.15.6 +// - protoc (unknown) // source: proto/users.proto package proto