├── .github └── workflows │ └── ci.yml ├── Makefile ├── README.md ├── cert ├── ca-cert.pem ├── ca-cert.srl ├── ca-key.pem ├── client-cert.pem ├── client-ext.cnf ├── client-key.pem ├── client-req.pem ├── gen.sh ├── server-cert.pem ├── server-ext.cnf ├── server-key.pem └── server-req.pem ├── client ├── auth_client.go ├── auth_interceptor.go └── laptop_client.go ├── cmd ├── client │ └── main.go └── server │ └── main.go ├── go.mod ├── go.sum ├── img └── .gitkeep ├── nginx.conf ├── pb ├── auth_service.pb.go ├── auth_service.pb.gw.go ├── auth_service_grpc.pb.go ├── filter_message.pb.go ├── keyboard_message.pb.go ├── laptop_message.pb.go ├── laptop_service.pb.go ├── laptop_service.pb.gw.go ├── laptop_service_grpc.pb.go ├── memory_message.pb.go ├── processor_message.pb.go ├── screen_message.pb.go └── storage_message.pb.go ├── proto ├── auth_service.proto ├── filter_message.proto ├── google │ ├── api │ │ ├── annotations.proto │ │ ├── http.proto │ │ └── httpbody.proto │ └── rpc │ │ ├── code.proto │ │ ├── error_details.proto │ │ └── status.proto ├── keyboard_message.proto ├── laptop_message.proto ├── laptop_service.proto ├── memory_message.proto ├── processor_message.proto ├── screen_message.proto └── storage_message.proto ├── sample ├── laptop.go └── random.go ├── serializer ├── file.go ├── file_test.go └── json.go ├── service ├── auth_interceptor.go ├── auth_server.go ├── image_store.go ├── jwt_manager.go ├── laptop_client_test.go ├── laptop_server.go ├── laptop_server_test.go ├── laptop_store.go ├── rating_store.go ├── user.go └── user_store.go ├── swagger ├── auth_service.swagger.json ├── filter_message.swagger.json ├── keyboard_message.swagger.json ├── laptop_message.swagger.json ├── laptop_service.swagger.json ├── memory_message.swagger.json ├── processor_message.swagger.json ├── screen_message.swagger.json └── storage_message.swagger.json └── tmp ├── laptop.bin ├── laptop.jpg └── laptop.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci-test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: ^1.15 21 | id: go 22 | 23 | - name: Check out code into the Go module directory 24 | uses: actions/checkout@v2 25 | 26 | - name: Test 27 | run: make test 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm pb/* 3 | rm swagger/* 4 | 5 | gen: 6 | protoc --proto_path=proto proto/*.proto --go_out=:pb --go-grpc_out=:pb --grpc-gateway_out=:pb --openapiv2_out=:swagger 7 | 8 | server1: 9 | go run cmd/server/main.go -port 50051 10 | 11 | server2: 12 | go run cmd/server/main.go -port 50052 13 | 14 | server1-tls: 15 | go run cmd/server/main.go -port 50051 -tls 16 | 17 | server2-tls: 18 | go run cmd/server/main.go -port 50052 -tls 19 | 20 | server: 21 | go run cmd/server/main.go -port 8080 22 | 23 | server-tls: 24 | go run cmd/server/main.go -port 8080 -tls 25 | 26 | rest: 27 | go run cmd/server/main.go -port 8081 -type rest -endpoint 0.0.0.0:8080 28 | 29 | client: 30 | go run cmd/client/main.go -address 0.0.0.0:8080 31 | 32 | client-tls: 33 | go run cmd/client/main.go -address 0.0.0.0:8080 -tls 34 | 35 | test: 36 | go test -cover -race ./... 37 | 38 | cert: 39 | cd cert; ./gen.sh; cd .. 40 | 41 | .PHONY: clean gen server client test cert 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PC Book - Go 2 | 3 | This repository contains the Golang codes for the hands-on section of [The complete gRPC course](http://bit.ly/grpccourse) by [TECH SCHOOL](https://dev.to/techschoolguru). 4 | 5 | ![The complete gRPC course](https://dev-to-uploads.s3.amazonaws.com/i/11r59di6zlyxf6g8o4s9.png) 6 | 7 | ## The complete gRPC course 8 | 9 | If you're building APIs for your microservices or mobile applications, you definitely want to try gRPC. It is super-fast, strongly-typed, and you no longer need to write a lot of boilerplate codes for services communication. Thanks to awesome HTTP/2 and Protocol Buffer! 10 | 11 | This is a 4-in-1 course, where you will learn not only gRPC, but also protocol-buffer and backend development with Go and Java. The codes in this course are production-grade, with well-organised structure and unit tests. 12 | 13 | ### What you’ll learn 14 | 15 | - What gRPC is, how it works, why we should use it, and where it is suitable to. 16 | - The amazing HTTP/2 protocol that gRPC is built on. 17 | - Compare gRPC with REST. 18 | - Write and serialise protocol-buffer messages using Go + Java. 19 | - Define gRPC services with protocol-buffer and generate Go + Java codes. 20 | - Implement 4 types of gRPC using Go + Java: unary, server-streaming, client-streaming, bidirectional streaming. 21 | - Handle context deadline, gRPC errors and status codes. 22 | - Write production-grade application with interfaces and unit tests for gRPC services. 23 | - Use gRPC interceptors to authenticate & authorise users with JWT. 24 | - Secure gRPC connection with sever-side & mutual SSL/TLS. 25 | - Enable gRPC reflections for service discovery. 26 | 27 | ### Are there any course requirements or prerequisites? 28 | 29 | - You only need to have basic programming skills in Go or Java. 30 | 31 | ## The PC book application 32 | 33 | PC book is an application to manage and search laptop configurations. It provides 4 gRPC APIs: 34 | 35 | 1. Create a new laptop: [unary gRPC](https://youtu.be/LOE_tkVFtb0) 36 | 37 | This is a unary RPC API that allows client to create a new laptop with some specific configurations. 38 | 39 | The input of the API is a laptop, and it returns the unique ID of the created laptop. 40 | 41 | The laptop ID is a UUID, and can be set by the client, or randomly generated by the server if it's not provided. 42 | 43 | 2. Search laptops with some filtering conditions: [server-streaming gRPC](https://youtu.be/SBPjEbZcgf8) 44 | 45 | This is a server-streaming RPC API that allows client to search for laptops that satisfies some filtering conditions, such as the maximum price, minimum cores, minimum CPU frequency, and minimum RAM. 46 | 47 | The input of the API is the filtering conditions, and it returns a stream of laptops that satisfy the conditions. 48 | 49 | 3. Upload a laptop image file in chunks: [client-streaming gRPC](https://youtu.be/i9H3BaRGLEc) 50 | 51 | This is a client-streaming RPC API that allows client to upload 1 laptop image file to the server. The file will be split into multiple chunks of 1 KB, and they will be sent to the server as a stream. 52 | 53 | The input of the API is a stream of request, which can either be: 54 | - Metadata of the image (only the 1st request): which contains the laptop ID, and the image type (or file extension) such as `.jpg` or `.png`. 55 | - Or a binary data chunk of the image. 56 | 57 | The total size of the image should not exceed 1 MB. 58 | 59 | The API will returns a response that contains the uploaded image ID (random UUID generated by the server) and the total size of the image. 60 | 61 | 4. Rate multiple laptops and get back average rating for each of them: [bidirectional-streaming gRPC](https://youtu.be/hjTI35iKMyQ) 62 | 63 | This is a bidirectional-streaming RPC API that allows client to rate multiple laptops, each with a score between 1 to 10, and get back the average rating score for each of them. 64 | 65 | The input of the API is a stream of requests, each with a laptop ID and a score. 66 | 67 | The API will returns a stream of responses, each contains a laptop ID, the number of times that laptop was rated, and the average rated score. 68 | 69 | ## Setup development environment 70 | 71 | - Install `protoc`: 72 | 73 | ```bash 74 | brew install protobuf 75 | ``` 76 | 77 | - Install `protoc-gen-go` and `protoc-gen-go-grpc` 78 | 79 | ```bash 80 | go get google.golang.org/protobuf/cmd/protoc-gen-go 81 | go get google.golang.org/grpc/cmd/protoc-gen-go-grpc 82 | ``` 83 | 84 | - Install `protoc-gen-grpc-gateway` and `protoc-gen-openapiv2` 85 | 86 | ```bash 87 | go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway 88 | go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 89 | ``` 90 | 91 | ## How to run 92 | 93 | - Generate codes from proto files: 94 | 95 | ```bash 96 | make gen 97 | ``` 98 | 99 | - Clean up generated codes in `pb` and `swagger` folders: 100 | 101 | ```bash 102 | make clean 103 | ``` 104 | 105 | - Run unit tests: 106 | 107 | ```bash 108 | make test 109 | ``` 110 | 111 | - Run server and client: 112 | 113 | ```bash 114 | make server 115 | make client 116 | ``` 117 | 118 | - Generate SSL/TLS certificates: 119 | 120 | ```bash 121 | make cert 122 | ``` 123 | 124 | ## TECH SCHOOL - From noob to pro 125 | 126 | At Tech School, we believe that everyone deserves a good and free education. The purpose of Tech School is to give everyone a chance to learn IT by giving free, high-quality tutorials and coding courses on Youtube. 127 | 128 | New videos are uploaded every week. The video topics are wide-ranging, and suitable for many different levels of tech knowledge: from noob to pro. The most important thing is: all content created by Tech School is free and will always be free. 129 | 130 | If you like the videos and want to support us with this vision, please share, subscribe, or [donate to us](https://donorbox.org/techschool). That would give us a lot of motivation to make more useful stuffs for the community. Thank you! 131 | -------------------------------------------------------------------------------- /cert/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFxjCCA64CCQDY35xYDFXtHjANBgkqhkiG9w0BAQsFADCBpDELMAkGA1UEBhMC 3 | RlIxEjAQBgNVBAgMCU9jY2l0YW5pZTERMA8GA1UEBwwIVG91bG91c2UxFDASBgNV 4 | BAoMC1RlY2ggU2Nob29sMRIwEAYDVQQLDAlFZHVjYXRpb24xGjAYBgNVBAMMESou 5 | dGVjaHNjaG9vbC5ndXJ1MSgwJgYJKoZIhvcNAQkBFhl0ZWNoc2Nob29sLmd1cnVA 6 | Z21haWwuY29tMB4XDTIwMDQxNTEwMjMwNVoXDTIxMDQxNTEwMjMwNVowgaQxCzAJ 7 | BgNVBAYTAkZSMRIwEAYDVQQIDAlPY2NpdGFuaWUxETAPBgNVBAcMCFRvdWxvdXNl 8 | MRQwEgYDVQQKDAtUZWNoIFNjaG9vbDESMBAGA1UECwwJRWR1Y2F0aW9uMRowGAYD 9 | VQQDDBEqLnRlY2hzY2hvb2wuZ3VydTEoMCYGCSqGSIb3DQEJARYZdGVjaHNjaG9v 10 | bC5ndXJ1QGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB 11 | AMlfsjdzwr9baKYRiL1VmbTSMxnCH7kQFYULl1073UqfkYrWSu3P8yiVSVu/EzUG 12 | wlN3C8wncRMn7JtBy3GuIjHnPC7t8E6ztcDWgear2RjzDQZmHVkcdAFji6hW2qEG 13 | LDIKUA2wfRXbjXnRqMj4ZZyIN6cHUJgo4+ZKwMus39WnwuWxQ/QNQhH432lfCHjj 14 | RiyA9AdJvObZVSUuHxfchbRR6aFFr8FL6/9hp5XTVfhz7iOEqWwXDwBQuBG8DHVt 15 | zp7i4BXx0DuTRhV31KAjz1nEdUFFZZB7rwcySAeL9OquObD8x1hehqkFU8F5z5JA 16 | O7nxWYOjTiRIVzLSwERi9TYAQbl8760jHlrn+0NDPAFXhsXmqYMYkNSJ486dNkT3 17 | yDr6kDeb4+r3bsN6g4GnXkXSRFcYUGMZA2k5tDJZbNd0DPMRV1QKy0Hz8R3JIJnN 18 | 2bD+tQ0iS+iLpLO2kybQqnrU/1iJGju1Sg4VM5R9xOtMBlewpYOu+3gPimsDq+uP 19 | jZg3GxQiBrfMBUJ35IlAHBw+yB/ObeEbcq3nZBQcetsVFIN3Qws6SlCxU3yFj+xk 20 | /gDOSJ0pPIuNlC5x9Rf9tV4LHZizE16hnf9Z4GzBW3Wu9hFw7ESqI11c++q1Lvq0 21 | b0Gou/TESjDlyoYE6ucKFz9I6ylqbjddCQFZl5u6AAF1AgMBAAEwDQYJKoZIhvcN 22 | AQELBQADggIBAKOD7Peqs5+BgBtX9EWk08rt29ALuO9lxxffcsAKSOYYCUOlwom4 23 | Uz2+l8hZgrLG631iQR+l9KzpbIt4V8c+kX+NUONGG6evSxLYev77hv2Z9cdOAfWF 24 | sP4jgigcUsH6Zs6gEQ6PeBf/XqNnghu4i9dcmjdOiYj2hx7e9baBwvrqnC5foqcZ 25 | NxqWkXZSidtD1iECIEJSIJYLwMdPy5cHVdAqyiFSjSWFDMccqvUIMOjIqyRbxEch 26 | 5UxGky7+3iS3r+UvTiloOkDDDOayYclc7CCBvknbLMnzWYcDoh+RiK4qQ/GWeEpr 27 | Yq02Mg5h9C/phE6mZln3A0AgFwJ2mSzXJHy2zAbSbXybCzDmojcykq+44TxuFP6z 28 | SXGzo6nkQ6om2BF0Z9GNlPFkaqMrBIo/6kctZlG1sptswlr5BkowbZEwLUAcji7k 29 | kFzquWkZeOU1zQ1ZBDllajh6zWzJCnyR0AmRz8p7iI35XA1vHRYXsZlfVSjC3P5d 30 | 0AiyoyyuInAzAtqPZUKSRAKUFkwyVvC48p4T/GxhgViNM/mn+OS905dt9DtBzTAw 31 | NkZ/SLYnRPpwGB7csw2EJgsTYRm5pg/QtA9pCmPib1IGT9SeLACJOMe+w0VAKuun 32 | I4pOaolfVC5dclS0rMxwC1S1wBp4YWDbnrxNa6lHRb+FDZBNwBGbfjry 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /cert/ca-cert.srl: -------------------------------------------------------------------------------- 1 | B141E873FD7B8575 2 | -------------------------------------------------------------------------------- /cert/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | uwawX4qaay9IGjuXdE8Py3mM3xpDXPA= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /cert/client-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 33 | 9BhkA0M0G3zQoGqSv94DTeI32qaxNg== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /cert/client-ext.cnf: -------------------------------------------------------------------------------- 1 | subjectAltName=DNS:*.pcclient.com,IP:0.0.0.0 2 | -------------------------------------------------------------------------------- /cert/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | ovQGWC8qf6sde8JLYDVa3WC36B1OwTQ= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /cert/client-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIE3DCCAsQCAQAwgZYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZBbHNhY2UxEzAR 3 | BgNVBAcMClN0cmFzYm91cmcxEjAQBgNVBAoMCVBDIENsaWVudDERMA8GA1UECwwI 4 | Q29tcHV0ZXIxFzAVBgNVBAMMDioucGNjbGllbnQuY29tMSEwHwYJKoZIhvcNAQkB 5 | FhJwY2NsaWVudEBnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK 6 | AoICAQDdQfxUWvq0/wEB+rOTjeRhd0p8Mgr9QC588Fm+gFxQSiPL8d/CYXql/IoK 7 | lsozjMyEzvOmDUBYbiMdfF7EUQjjxt5XAnkep/P7rtlchD1HDX0RxobPiXZk942X 8 | qJGn8LAkH8CuQNon2KlN5JRJU8MFcA/R4E0Dp6joFxMM6QtNRMyL0LUev+PQDTiV 9 | FEXzvGntk4JV7/dMkwSPLuP5VdnSj1o5sbJ1KWnSoSkhxlOXqE9w8oNCk4oNaV2B 10 | COmlvqgV1tAf5aKJ3n5zdlv07yfbtBPWTlhK/BJKc+woOKbGbhk3+jzL4xBCwnJv 11 | aU/4wIzjqpinmWFO5s3nLLs5hIuHd1+V7Jw13di7md1RYlvbHadEWQ4FkxeydTCf 12 | ZIOy4XOOs4bay06pQRYrElTFhi74aao1UwVCFgrgHisVZGYErVYrc65MZOAL+XWC 13 | OVyagwrMJhCFpzykl7eAfEtBdbGAlmHePhg/c5AoMT0rwjbBjCq2keBNzPhHAURC 14 | VHs8QLyTecj7q/tPPq9IbEAu6eCzE2ZVS7ATgAhgCwn0JCYTNcnATQQtQELEZKp7 15 | FbsnZamE3n/KUgg1KahqC5KJKAtgXwFRM7h0UvD6DRF+tJcFPqaawphrWykLhcEF 16 | F9xc8jFobQPTklq3w80oW+At7JSrtqZP6cOidnwHZMQHtsAQaQIDAQABoAAwDQYJ 17 | KoZIhvcNAQELBQADggIBAHUPxGmovTlpVp6d9tOv2RbrqOq/9XDrul6O21/wsU0A 18 | ZWlDyIHCrxjlTg8YrrHl1tiJYpi03pb707j6pjX6Q/AWLrTHuLo8l2cDnDu+xL08 19 | SWZ20DtmM5GnYdmJzjY6mR6b5lYDrGlqFaynKCBn2IgB0AJeraGSsh/+cnhOgzya 20 | 6nKm8bOM9DSkcuki5b9JTYjtnmSUlwaaup0rlkKtnt0XLV4jKcQL4DurYfSXUXgs 21 | sazbKj/N2xkRiSyk40lf5ASPFAyXDOr57c+IMKokhcWZvZunZc+reqWXkwvJQwZ9 22 | 8UNsrHmNopDbQI1mLk4Ky8cPRWbtdyjnJlwPc59TbGmdVNWm6KMmCwXqiegORivo 23 | fwhOPEXvTusrcwgyREtS+bYOG0sFVGce6nxcIjBf9sY+igXGUTm9UyQVGO0YDLpJ 24 | hJLL5bb9iv4nurokFWuoGI0H3XpeiLXBO6oPA/PXSEru6deP9/HwtGADVcoxQFa0 25 | fLn9bA7lyyIXm6UpQXIAMv/Ptqv7v0SoesrwpxloJQKa43Iv0CkaFrWfLedCdayR 26 | BgqOMvFcN6pzfCnaEpFb/CppBPP7Q/wVhAuIlmVq7M75q6/rW819RLxZFaNF0Yyx 27 | 0P5n1b8sSlUuQPPG0JNHGDZbM8Jz7iKMhcJuH8CQGI3EqPJVM3onvlYV1AWDKWI3 28 | -----END CERTIFICATE REQUEST----- 29 | -------------------------------------------------------------------------------- /cert/gen.sh: -------------------------------------------------------------------------------- 1 | rm *.pem 2 | 3 | # 1. Generate CA's private key and self-signed certificate 4 | openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=FR/ST=Occitanie/L=Toulouse/O=Tech School/OU=Education/CN=*.techschool.guru/emailAddress=techschool.guru@gmail.com" 5 | 6 | echo "CA's self-signed certificate" 7 | openssl x509 -in ca-cert.pem -noout -text 8 | 9 | # 2. Generate web server's private key and certificate signing request (CSR) 10 | openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=FR/ST=Ile de France/L=Paris/O=PC Book/OU=Computer/CN=*.pcbook.com/emailAddress=pcbook@gmail.com" 11 | 12 | # 3. Use CA's private key to sign web server's CSR and get back the signed certificate 13 | openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf 14 | 15 | echo "Server's signed certificate" 16 | openssl x509 -in server-cert.pem -noout -text 17 | 18 | # 4. Generate client's private key and certificate signing request (CSR) 19 | openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=FR/ST=Alsace/L=Strasbourg/O=PC Client/OU=Computer/CN=*.pcclient.com/emailAddress=pcclient@gmail.com" 20 | 21 | # 5. Use CA's private key to sign client's CSR and get back the signed certificate 22 | openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf 23 | 24 | echo "Client's signed certificate" 25 | openssl x509 -in client-cert.pem -noout -text 26 | -------------------------------------------------------------------------------- /cert/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF6jCCA9KgAwIBAgIJALFB6HP9e4V0MA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD 3 | VQQGEwJGUjESMBAGA1UECAwJT2NjaXRhbmllMREwDwYDVQQHDAhUb3Vsb3VzZTEU 4 | MBIGA1UECgwLVGVjaCBTY2hvb2wxEjAQBgNVBAsMCUVkdWNhdGlvbjEaMBgGA1UE 5 | AwwRKi50ZWNoc2Nob29sLmd1cnUxKDAmBgkqhkiG9w0BCQEWGXRlY2hzY2hvb2wu 6 | Z3VydUBnbWFpbC5jb20wHhcNMjAwNDE1MTAyMzA3WhcNMjAwNjE0MTAyMzA3WjCB 7 | kjELMAkGA1UEBhMCRlIxFjAUBgNVBAgMDUlsZSBkZSBGcmFuY2UxDjAMBgNVBAcM 8 | BVBhcmlzMRAwDgYDVQQKDAdQQyBCb29rMREwDwYDVQQLDAhDb21wdXRlcjEVMBMG 9 | A1UEAwwMKi5wY2Jvb2suY29tMR8wHQYJKoZIhvcNAQkBFhBwY2Jvb2tAZ21haWwu 10 | Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwOsO3r/3ul2O2W1k 11 | ka9j7VYLmXcBOY3Lkoo+k4N6dwLXH9NADO5cBQsLFyljHZ+gwbeV0pRDTs/PFwNA 12 | ovHToNA6I2Uw9TI3Yz/MzUlZ4PpWkhdpYEnXWOgccXeKTFyYX4oKCGiTYLyDV7DH 13 | E9kdMPX+sz7aiayloUKzOEVdLFtUIH2V8e9U5j7zKWAGoPCqbWYRGoAG6y8i3udi 14 | 6pS/7ETq0MVI+QXcQ8+eOrzNAZjkagMhEKB1czLFh7bb3YtHz/fl/Sujqn21oKWI 15 | Y8CAHuMxYFMZO6im0QkztiL35vRPyozhi+Kr2LIaN4mfCKBjps13uGvYS8fDZbDD 16 | +F8ix/Avhbz4PWEm2S5QdDSz44/69sbFKLk3qqoz0tO4byu5ADjIsnba9/9ZeqNx 17 | 3IiIXkPKMfNqfgq1Ti3M90aWW2uPF96AXWc/MKKHErA/AqgOn8XTBjzi/VBenoHH 18 | NrsNoz0j5wV7i5SBPAGEOu5s7jV7hc5B5geWIpLOklUzryGiv5IRsM3U/dV9qYry 19 | 5GPywW0usxyyUKvTCe/GLt+63h+0xdGTbPs4Rd0twX0V1ZOpzMjnpaYtmczl+x8S 20 | R0ZsTzyLhXsCzUPY5yNuGBNKIXBtwedZCEIZ3vT23lzvEgagUhqkymocVi9lXL+Z 21 | U8I6Ketb4FHIjVtvCCZj8FVvHE0CAwEAAaMvMC0wKwYDVR0RBCQwIoIMKi5wY2Jv 22 | b2suY29tggwqLnBjYm9vay5vcmeHBAAAAAAwDQYJKoZIhvcNAQEFBQADggIBABmv 23 | Ce/pzmsilW9rFKPnZyr4zjdevi3zK4hn78iT2eCMoH48UigoFERphtqtnoinmHQD 24 | qMFRfJ7ZBx6wPYo/fgxp+sXjMKT+hWUtU2CNQoVbeH2dpKU7xeyh82Q7fesajIYi 25 | UrpuBlYBjZywNPJrpSKJyaq4H37Yt60SCA8GUrXb5LHcurRSJNtIQ8muOVgwvjAY 26 | 1mzADfOW2wlVX84oyXBHFo+3iXvjKhp8AahTCiQak4MvJwMPuiUJf8qIaqNi1w0s 27 | Ot5p4QtLECYrfJxtsmyGRCOrCWdXpj2oPY85bz0CMdf7IuufFoTarhBm1E71YhOl 28 | Sgrnj3w89HxIYDG/DvaybXO4/bs62oyQzcLzgeyoJeRLp23LpSR+8o4OV9koKUyQ 29 | DBMNikKIqk+pZWEiJ6uILYUH1Ay8BBkUt/XY5Eeuy5RPtBrPJENHNAwgN5HU+h5i 30 | glOc/rCpgYWWIVeHeickMxr3lZXVel9viNAucmLu9OuKi/jHksJcqimMDAwZGlnj 31 | waeLHv1Ea/DX24lUkuA2m5w9RCcNI2ZmTmL7svwZrkdNmLLkcqz2nWBFyNZC5+P3 32 | pN4Qe/thzJWsss1JB/Kn6V/2NX2/yr6eKrjt8jo5rucUbdtysOYrjCMBm0RiCJbK 33 | 3I3Nv1KsekkLnFiQIW7uXwLl5FJcaXHLfvYZlusV 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /cert/server-ext.cnf: -------------------------------------------------------------------------------- 1 | subjectAltName=DNS:*.pcbook.com,DNS:*.pcbook.org,IP:0.0.0.0 2 | -------------------------------------------------------------------------------- /cert/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDA6w7ev/e6XY7Z 3 | bWSRr2PtVguZdwE5jcuSij6Tg3p3Atcf00AM7lwFCwsXKWMdn6DBt5XSlENOz88X 4 | A0Ci8dOg0DojZTD1MjdjP8zNSVng+laSF2lgSddY6Bxxd4pMXJhfigoIaJNgvINX 5 | sMcT2R0w9f6zPtqJrKWhQrM4RV0sW1QgfZXx71TmPvMpYAag8KptZhEagAbrLyLe 6 | 52LqlL/sROrQxUj5BdxDz546vM0BmORqAyEQoHVzMsWHttvdi0fP9+X9K6OqfbWg 7 | pYhjwIAe4zFgUxk7qKbRCTO2Ivfm9E/KjOGL4qvYsho3iZ8IoGOmzXe4a9hLx8Nl 8 | sMP4XyLH8C+FvPg9YSbZLlB0NLPjj/r2xsUouTeqqjPS07hvK7kAOMiydtr3/1l6 9 | o3HciIheQ8ox82p+CrVOLcz3RpZba48X3oBdZz8woocSsD8CqA6fxdMGPOL9UF6e 10 | gcc2uw2jPSPnBXuLlIE8AYQ67mzuNXuFzkHmB5Yiks6SVTOvIaK/khGwzdT91X2p 11 | ivLkY/LBbS6zHLJQq9MJ78Yu37reH7TF0ZNs+zhF3S3BfRXVk6nMyOelpi2ZzOX7 12 | HxJHRmxPPIuFewLNQ9jnI24YE0ohcG3B51kIQhne9PbeXO8SBqBSGqTKahxWL2Vc 13 | v5lTwjop61vgUciNW28IJmPwVW8cTQIDAQABAoICAQCQuYZdSuxAbmF08aEJvecc 14 | LHnlNibAE4TNuVI6fd8ImyPhpywcx1BXJDK8vHqzxYXm7Z/C6yEXZcR5AiKiWwKl 15 | WLDUztwMhhCRL1KoCsgXhBYf4NpXtu2LsA1uffxNTwWsXrUqG7G8V4+84Exosm84 16 | xMK/m3582/0hXhVvOHIujZEuEqjDaAVr+XuX5Ybzg7iG+5QHKlaGZsUlLbbCPrdA 17 | O29hES+uSVw1rvKIJA0zjoyEjzZl78pMkqEnL+H/cLZ96P4rkGpmw4nXK3eHRemX 18 | wl7PYWfDnsEOfnXBxLfvFgcp78hglbrPhMUwNtkMsq4ve2K+AoGwT/thNVu+3zgK 19 | Mj/L+QSOKW7uY+E5oaETy7lg5P+65++4/iGyuszp2zX/iPMJcYwDoYcIwmIYkyIZ 20 | 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 51 | mQ46mZhKvL1F0M1v7dIRGpBcXSwHnIQ= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /cert/server-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIE2DCCAsACAQAwgZIxCzAJBgNVBAYTAkZSMRYwFAYDVQQIDA1JbGUgZGUgRnJh 3 | bmNlMQ4wDAYDVQQHDAVQYXJpczEQMA4GA1UECgwHUEMgQm9vazERMA8GA1UECwwI 4 | Q29tcHV0ZXIxFTATBgNVBAMMDCoucGNib29rLmNvbTEfMB0GCSqGSIb3DQEJARYQ 5 | cGNib29rQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB 6 | AMDrDt6/97pdjtltZJGvY+1WC5l3ATmNy5KKPpODencC1x/TQAzuXAULCxcpYx2f 7 | oMG3ldKUQ07PzxcDQKLx06DQOiNlMPUyN2M/zM1JWeD6VpIXaWBJ11joHHF3ikxc 8 | mF+KCghok2C8g1ewxxPZHTD1/rM+2omspaFCszhFXSxbVCB9lfHvVOY+8ylgBqDw 9 | qm1mERqABusvIt7nYuqUv+xE6tDFSPkF3EPPnjq8zQGY5GoDIRCgdXMyxYe2292L 10 | R8/35f0ro6p9taCliGPAgB7jMWBTGTuoptEJM7Yi9+b0T8qM4Yviq9iyGjeJnwig 11 | Y6bNd7hr2EvHw2Www/hfIsfwL4W8+D1hJtkuUHQ0s+OP+vbGxSi5N6qqM9LTuG8r 12 | uQA4yLJ22vf/WXqjcdyIiF5DyjHzan4KtU4tzPdGlltrjxfegF1nPzCihxKwPwKo 13 | Dp/F0wY84v1QXp6Bxza7DaM9I+cFe4uUgTwBhDrubO41e4XOQeYHliKSzpJVM68h 14 | or+SEbDN1P3VfamK8uRj8sFtLrMcslCr0wnvxi7fut4ftMXRk2z7OEXdLcF9FdWT 15 | qczI56WmLZnM5fsfEkdGbE88i4V7As1D2OcjbhgTSiFwbcHnWQhCGd709t5c7xIG 16 | oFIapMpqHFYvZVy/mVPCOinrW+BRyI1bbwgmY/BVbxxNAgMBAAGgADANBgkqhkiG 17 | 9w0BAQsFAAOCAgEAH+8i1ADYuh3KgEFmEOgmhBe2waanpC7bBOaPCuLlXOhJcZnb 18 | TPTPVx9zBShPuaQS+xgobpRxBlataHKw2a/YoO8lPklglAcR188uRToIHjD/oeEa 19 | nAwOE+FZHcxj7lkEWTjKTFlCNkEuWYm2QqEpYZ9jTRw23JyU6zHHZU9TRPRBNReL 20 | hudzJOB4ZN+DZOOvhEUEOxNhVJDvPo9jldS7Ha0Xg8Q7AMxbmc7LYFx11/f2Oejv 21 | PJL3HYi2WGRp2Ie81Heu8CgX5KEt6gu8EbLmTopQsVcU9CirO5klgFtJf3YzAMYz 22 | 4jZqbwR3N2WYGvloT2DrMvMhX4m+d0eC8I5M7JIW+GzUO7h4szvhSJ2Vw9c1rcGP 23 | g6Y6RxEyG/GJtaG3SII8M+nXbPG4D/ZDYlh4YP0wWxeQZimifpAPmVpYlUG1nKgC 24 | erHoOIw1INI9M5jgbFNEcTE/GLqficWjypq6lBeV5aZo/rXgyQFoxHI1N2lRbVfN 25 | dEA+2D89le6VCizjHIsTDyY9tqGkRopsvm78DEwSmCBHnxRiky9A4MTRApAKoJDJ 26 | Q/7AxbY7rhcWmwn+GZDoP5aY1aLe86Y1w3QR2VWKeXOKQeepRsOs8QTAPJoowpdy 27 | fWlZKRZdDjhiwTCNztFL72KV7x0DcLVowYRJfrrK0MQdoosBs2DR74bztEg= 28 | -----END CERTIFICATE REQUEST----- 29 | -------------------------------------------------------------------------------- /client/auth_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "gitlab.com/techschool/pcbook/pb" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // AuthClient is a client to call authentication RPC 12 | type AuthClient struct { 13 | service pb.AuthServiceClient 14 | username string 15 | password string 16 | } 17 | 18 | // NewAuthClient returns a new auth client 19 | func NewAuthClient(cc *grpc.ClientConn, username string, password string) *AuthClient { 20 | service := pb.NewAuthServiceClient(cc) 21 | return &AuthClient{service, username, password} 22 | } 23 | 24 | // Login login user and returns the access token 25 | func (client *AuthClient) Login() (string, error) { 26 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 27 | defer cancel() 28 | 29 | req := &pb.LoginRequest{ 30 | Username: client.username, 31 | Password: client.password, 32 | } 33 | 34 | res, err := client.service.Login(ctx, req) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | return res.GetAccessToken(), nil 40 | } 41 | -------------------------------------------------------------------------------- /client/auth_interceptor.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/metadata" 10 | ) 11 | 12 | // AuthInterceptor is a client interceptor for authentication 13 | type AuthInterceptor struct { 14 | authClient *AuthClient 15 | authMethods map[string]bool 16 | accessToken string 17 | } 18 | 19 | // NewAuthInterceptor returns a new auth interceptor 20 | func NewAuthInterceptor( 21 | authClient *AuthClient, 22 | authMethods map[string]bool, 23 | refreshDuration time.Duration, 24 | ) (*AuthInterceptor, error) { 25 | interceptor := &AuthInterceptor{ 26 | authClient: authClient, 27 | authMethods: authMethods, 28 | } 29 | 30 | err := interceptor.scheduleRefreshToken(refreshDuration) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return interceptor, nil 36 | } 37 | 38 | // Unary returns a client interceptor to authenticate unary RPC 39 | func (interceptor *AuthInterceptor) Unary() grpc.UnaryClientInterceptor { 40 | return func( 41 | ctx context.Context, 42 | method string, 43 | req, reply interface{}, 44 | cc *grpc.ClientConn, 45 | invoker grpc.UnaryInvoker, 46 | opts ...grpc.CallOption, 47 | ) error { 48 | log.Printf("--> unary interceptor: %s", method) 49 | 50 | if interceptor.authMethods[method] { 51 | return invoker(interceptor.attachToken(ctx), method, req, reply, cc, opts...) 52 | } 53 | 54 | return invoker(ctx, method, req, reply, cc, opts...) 55 | } 56 | } 57 | 58 | // Stream returns a client interceptor to authenticate stream RPC 59 | func (interceptor *AuthInterceptor) Stream() grpc.StreamClientInterceptor { 60 | return func( 61 | ctx context.Context, 62 | desc *grpc.StreamDesc, 63 | cc *grpc.ClientConn, 64 | method string, 65 | streamer grpc.Streamer, 66 | opts ...grpc.CallOption, 67 | ) (grpc.ClientStream, error) { 68 | log.Printf("--> stream interceptor: %s", method) 69 | 70 | if interceptor.authMethods[method] { 71 | return streamer(interceptor.attachToken(ctx), desc, cc, method, opts...) 72 | } 73 | 74 | return streamer(ctx, desc, cc, method, opts...) 75 | } 76 | } 77 | 78 | func (interceptor *AuthInterceptor) attachToken(ctx context.Context) context.Context { 79 | return metadata.AppendToOutgoingContext(ctx, "authorization", interceptor.accessToken) 80 | } 81 | 82 | func (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Duration) error { 83 | err := interceptor.refreshToken() 84 | if err != nil { 85 | return err 86 | } 87 | 88 | go func() { 89 | wait := refreshDuration 90 | for { 91 | time.Sleep(wait) 92 | err := interceptor.refreshToken() 93 | if err != nil { 94 | wait = time.Second 95 | } else { 96 | wait = refreshDuration 97 | } 98 | } 99 | }() 100 | 101 | return nil 102 | } 103 | 104 | func (interceptor *AuthInterceptor) refreshToken() error { 105 | accessToken, err := interceptor.authClient.Login() 106 | if err != nil { 107 | return err 108 | } 109 | 110 | interceptor.accessToken = accessToken 111 | log.Printf("token refreshed: %v", accessToken) 112 | 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /client/laptop_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "gitlab.com/techschool/pcbook/pb" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | ) 18 | 19 | // LaptopClient is a client to call laptop service RPCs 20 | type LaptopClient struct { 21 | service pb.LaptopServiceClient 22 | } 23 | 24 | // NewLaptopClient returns a new laptop client 25 | func NewLaptopClient(cc *grpc.ClientConn) *LaptopClient { 26 | service := pb.NewLaptopServiceClient(cc) 27 | return &LaptopClient{service} 28 | } 29 | 30 | // CreateLaptop calls create laptop RPC 31 | func (laptopClient *LaptopClient) CreateLaptop(laptop *pb.Laptop) { 32 | req := &pb.CreateLaptopRequest{ 33 | Laptop: laptop, 34 | } 35 | 36 | // set timeout 37 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 38 | defer cancel() 39 | 40 | res, err := laptopClient.service.CreateLaptop(ctx, req) 41 | if err != nil { 42 | st, ok := status.FromError(err) 43 | if ok && st.Code() == codes.AlreadyExists { 44 | // not a big deal 45 | log.Print("laptop already exists") 46 | } else { 47 | log.Fatal("cannot create laptop: ", err) 48 | } 49 | return 50 | } 51 | 52 | log.Printf("created laptop with id: %s", res.Id) 53 | } 54 | 55 | // SearchLaptop calls search laptop RPC 56 | func (laptopClient *LaptopClient) SearchLaptop(filter *pb.Filter) { 57 | log.Print("search filter: ", filter) 58 | 59 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 60 | defer cancel() 61 | 62 | req := &pb.SearchLaptopRequest{Filter: filter} 63 | stream, err := laptopClient.service.SearchLaptop(ctx, req) 64 | if err != nil { 65 | log.Fatal("cannot search laptop: ", err) 66 | } 67 | 68 | for { 69 | res, err := stream.Recv() 70 | if err == io.EOF { 71 | return 72 | } 73 | if err != nil { 74 | log.Fatal("cannot receive response: ", err) 75 | } 76 | 77 | laptop := res.GetLaptop() 78 | log.Print("- found: ", laptop.GetId()) 79 | log.Print(" + brand: ", laptop.GetBrand()) 80 | log.Print(" + name: ", laptop.GetName()) 81 | log.Print(" + cpu cores: ", laptop.GetCpu().GetNumberCores()) 82 | log.Print(" + cpu min ghz: ", laptop.GetCpu().GetMinGhz()) 83 | log.Print(" + ram: ", laptop.GetRam()) 84 | log.Print(" + price: ", laptop.GetPriceUsd()) 85 | } 86 | } 87 | 88 | // UploadImage calls upload image RPC 89 | func (laptopClient *LaptopClient) UploadImage(laptopID string, imagePath string) { 90 | file, err := os.Open(imagePath) 91 | if err != nil { 92 | log.Fatal("cannot open image file: ", err) 93 | } 94 | defer file.Close() 95 | 96 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 97 | defer cancel() 98 | 99 | stream, err := laptopClient.service.UploadImage(ctx) 100 | if err != nil { 101 | log.Fatal("cannot upload image: ", err) 102 | } 103 | 104 | req := &pb.UploadImageRequest{ 105 | Data: &pb.UploadImageRequest_Info{ 106 | Info: &pb.ImageInfo{ 107 | LaptopId: laptopID, 108 | ImageType: filepath.Ext(imagePath), 109 | }, 110 | }, 111 | } 112 | 113 | err = stream.Send(req) 114 | if err != nil { 115 | log.Fatal("cannot send image info to server: ", err, stream.RecvMsg(nil)) 116 | } 117 | 118 | reader := bufio.NewReader(file) 119 | buffer := make([]byte, 1024) 120 | 121 | for { 122 | n, err := reader.Read(buffer) 123 | if err == io.EOF { 124 | break 125 | } 126 | if err != nil { 127 | log.Fatal("cannot read chunk to buffer: ", err) 128 | } 129 | 130 | req := &pb.UploadImageRequest{ 131 | Data: &pb.UploadImageRequest_ChunkData{ 132 | ChunkData: buffer[:n], 133 | }, 134 | } 135 | 136 | err = stream.Send(req) 137 | if err != nil { 138 | log.Fatal("cannot send chunk to server: ", err, stream.RecvMsg(nil)) 139 | } 140 | } 141 | 142 | res, err := stream.CloseAndRecv() 143 | if err != nil { 144 | log.Fatal("cannot receive response: ", err) 145 | } 146 | 147 | log.Printf("image uploaded with id: %s, size: %d", res.GetId(), res.GetSize()) 148 | } 149 | 150 | // RateLaptop calls rate laptop RPC 151 | func (laptopClient *LaptopClient) RateLaptop(laptopIDs []string, scores []float64) error { 152 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 153 | defer cancel() 154 | 155 | stream, err := laptopClient.service.RateLaptop(ctx) 156 | if err != nil { 157 | return fmt.Errorf("cannot rate laptop: %v", err) 158 | } 159 | 160 | waitResponse := make(chan error) 161 | // go routine to receive responses 162 | go func() { 163 | for { 164 | res, err := stream.Recv() 165 | if err == io.EOF { 166 | log.Print("no more responses") 167 | waitResponse <- nil 168 | return 169 | } 170 | if err != nil { 171 | waitResponse <- fmt.Errorf("cannot receive stream response: %v", err) 172 | return 173 | } 174 | 175 | log.Print("received response: ", res) 176 | } 177 | }() 178 | 179 | // send requests 180 | for i, laptopID := range laptopIDs { 181 | req := &pb.RateLaptopRequest{ 182 | LaptopId: laptopID, 183 | Score: scores[i], 184 | } 185 | 186 | err := stream.Send(req) 187 | if err != nil { 188 | return fmt.Errorf("cannot send stream request: %v - %v", err, stream.RecvMsg(nil)) 189 | } 190 | 191 | log.Print("sent request: ", req) 192 | } 193 | 194 | err = stream.CloseSend() 195 | if err != nil { 196 | return fmt.Errorf("cannot close send: %v", err) 197 | } 198 | 199 | err = <-waitResponse 200 | return err 201 | } 202 | -------------------------------------------------------------------------------- /cmd/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "strings" 11 | "time" 12 | 13 | "gitlab.com/techschool/pcbook/client" 14 | "gitlab.com/techschool/pcbook/pb" 15 | "gitlab.com/techschool/pcbook/sample" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials" 18 | ) 19 | 20 | func testCreateLaptop(laptopClient *client.LaptopClient) { 21 | laptopClient.CreateLaptop(sample.NewLaptop()) 22 | } 23 | 24 | func testSearchLaptop(laptopClient *client.LaptopClient) { 25 | for i := 0; i < 10; i++ { 26 | laptopClient.CreateLaptop(sample.NewLaptop()) 27 | } 28 | 29 | filter := &pb.Filter{ 30 | MaxPriceUsd: 3000, 31 | MinCpuCores: 4, 32 | MinCpuGhz: 2.5, 33 | MinRam: &pb.Memory{Value: 8, Unit: pb.Memory_GIGABYTE}, 34 | } 35 | 36 | laptopClient.SearchLaptop(filter) 37 | } 38 | 39 | func testUploadImage(laptopClient *client.LaptopClient) { 40 | laptop := sample.NewLaptop() 41 | laptopClient.CreateLaptop(laptop) 42 | laptopClient.UploadImage(laptop.GetId(), "tmp/laptop.jpg") 43 | } 44 | 45 | func testRateLaptop(laptopClient *client.LaptopClient) { 46 | n := 3 47 | laptopIDs := make([]string, n) 48 | 49 | for i := 0; i < n; i++ { 50 | laptop := sample.NewLaptop() 51 | laptopIDs[i] = laptop.GetId() 52 | laptopClient.CreateLaptop(laptop) 53 | } 54 | 55 | scores := make([]float64, n) 56 | for { 57 | fmt.Print("rate laptop (y/n)? ") 58 | var answer string 59 | fmt.Scan(&answer) 60 | 61 | if strings.ToLower(answer) != "y" { 62 | break 63 | } 64 | 65 | for i := 0; i < n; i++ { 66 | scores[i] = sample.RandomLaptopScore() 67 | } 68 | 69 | err := laptopClient.RateLaptop(laptopIDs, scores) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | } 74 | } 75 | 76 | const ( 77 | username = "admin1" 78 | password = "secret" 79 | refreshDuration = 30 * time.Second 80 | ) 81 | 82 | func authMethods() map[string]bool { 83 | const laptopServicePath = "/techschool.pcbook.LaptopService/" 84 | 85 | return map[string]bool{ 86 | laptopServicePath + "CreateLaptop": true, 87 | laptopServicePath + "UploadImage": true, 88 | laptopServicePath + "RateLaptop": true, 89 | } 90 | } 91 | 92 | func loadTLSCredentials() (credentials.TransportCredentials, error) { 93 | // Load certificate of the CA who signed server's certificate 94 | pemServerCA, err := ioutil.ReadFile("cert/ca-cert.pem") 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | certPool := x509.NewCertPool() 100 | if !certPool.AppendCertsFromPEM(pemServerCA) { 101 | return nil, fmt.Errorf("failed to add server CA's certificate") 102 | } 103 | 104 | // Load client's certificate and private key 105 | clientCert, err := tls.LoadX509KeyPair("cert/client-cert.pem", "cert/client-key.pem") 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | // Create the credentials and return it 111 | config := &tls.Config{ 112 | Certificates: []tls.Certificate{clientCert}, 113 | RootCAs: certPool, 114 | } 115 | 116 | return credentials.NewTLS(config), nil 117 | } 118 | 119 | func main() { 120 | serverAddress := flag.String("address", "", "the server address") 121 | enableTLS := flag.Bool("tls", false, "enable SSL/TLS") 122 | flag.Parse() 123 | log.Printf("dial server %s, TLS = %t", *serverAddress, *enableTLS) 124 | 125 | transportOption := grpc.WithInsecure() 126 | 127 | if *enableTLS { 128 | tlsCredentials, err := loadTLSCredentials() 129 | if err != nil { 130 | log.Fatal("cannot load TLS credentials: ", err) 131 | } 132 | 133 | transportOption = grpc.WithTransportCredentials(tlsCredentials) 134 | } 135 | 136 | cc1, err := grpc.Dial(*serverAddress, transportOption) 137 | if err != nil { 138 | log.Fatal("cannot dial server: ", err) 139 | } 140 | 141 | authClient := client.NewAuthClient(cc1, username, password) 142 | interceptor, err := client.NewAuthInterceptor(authClient, authMethods(), refreshDuration) 143 | if err != nil { 144 | log.Fatal("cannot create auth interceptor: ", err) 145 | } 146 | 147 | cc2, err := grpc.Dial( 148 | *serverAddress, 149 | transportOption, 150 | grpc.WithUnaryInterceptor(interceptor.Unary()), 151 | grpc.WithStreamInterceptor(interceptor.Stream()), 152 | ) 153 | if err != nil { 154 | log.Fatal("cannot dial server: ", err) 155 | } 156 | 157 | laptopClient := client.NewLaptopClient(cc2) 158 | testRateLaptop(laptopClient) 159 | } 160 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net" 12 | "net/http" 13 | "time" 14 | 15 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 16 | "gitlab.com/techschool/pcbook/pb" 17 | "gitlab.com/techschool/pcbook/service" 18 | "google.golang.org/grpc" 19 | "google.golang.org/grpc/credentials" 20 | "google.golang.org/grpc/reflection" 21 | ) 22 | 23 | func seedUsers(userStore service.UserStore) error { 24 | err := createUser(userStore, "admin1", "secret", "admin") 25 | if err != nil { 26 | return err 27 | } 28 | return createUser(userStore, "user1", "secret", "user") 29 | } 30 | 31 | func createUser(userStore service.UserStore, username, password, role string) error { 32 | user, err := service.NewUser(username, password, role) 33 | if err != nil { 34 | return err 35 | } 36 | return userStore.Save(user) 37 | } 38 | 39 | const ( 40 | secretKey = "secret" 41 | tokenDuration = 15 * time.Minute 42 | ) 43 | 44 | const ( 45 | serverCertFile = "cert/server-cert.pem" 46 | serverKeyFile = "cert/server-key.pem" 47 | clientCACertFile = "cert/ca-cert.pem" 48 | ) 49 | 50 | func accessibleRoles() map[string][]string { 51 | const laptopServicePath = "/techschool.pcbook.LaptopService/" 52 | 53 | return map[string][]string{ 54 | laptopServicePath + "CreateLaptop": {"admin"}, 55 | laptopServicePath + "UploadImage": {"admin"}, 56 | laptopServicePath + "RateLaptop": {"admin", "user"}, 57 | } 58 | } 59 | 60 | func loadTLSCredentials() (credentials.TransportCredentials, error) { 61 | // Load certificate of the CA who signed client's certificate 62 | pemClientCA, err := ioutil.ReadFile(clientCACertFile) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | certPool := x509.NewCertPool() 68 | if !certPool.AppendCertsFromPEM(pemClientCA) { 69 | return nil, fmt.Errorf("failed to add client CA's certificate") 70 | } 71 | 72 | // Load server's certificate and private key 73 | serverCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | // Create the credentials and return it 79 | config := &tls.Config{ 80 | Certificates: []tls.Certificate{serverCert}, 81 | ClientAuth: tls.RequireAndVerifyClientCert, 82 | ClientCAs: certPool, 83 | } 84 | 85 | return credentials.NewTLS(config), nil 86 | } 87 | 88 | func runGRPCServer( 89 | authServer pb.AuthServiceServer, 90 | laptopServer pb.LaptopServiceServer, 91 | jwtManager *service.JWTManager, 92 | enableTLS bool, 93 | listener net.Listener, 94 | ) error { 95 | interceptor := service.NewAuthInterceptor(jwtManager, accessibleRoles()) 96 | serverOptions := []grpc.ServerOption{ 97 | grpc.UnaryInterceptor(interceptor.Unary()), 98 | grpc.StreamInterceptor(interceptor.Stream()), 99 | } 100 | 101 | if enableTLS { 102 | tlsCredentials, err := loadTLSCredentials() 103 | if err != nil { 104 | return fmt.Errorf("cannot load TLS credentials: %w", err) 105 | } 106 | 107 | serverOptions = append(serverOptions, grpc.Creds(tlsCredentials)) 108 | } 109 | 110 | grpcServer := grpc.NewServer(serverOptions...) 111 | 112 | pb.RegisterAuthServiceServer(grpcServer, authServer) 113 | pb.RegisterLaptopServiceServer(grpcServer, laptopServer) 114 | reflection.Register(grpcServer) 115 | 116 | log.Printf("Start GRPC server at %s, TLS = %t", listener.Addr().String(), enableTLS) 117 | return grpcServer.Serve(listener) 118 | } 119 | 120 | func runRESTServer( 121 | authServer pb.AuthServiceServer, 122 | laptopServer pb.LaptopServiceServer, 123 | jwtManager *service.JWTManager, 124 | enableTLS bool, 125 | listener net.Listener, 126 | grpcEndpoint string, 127 | ) error { 128 | mux := runtime.NewServeMux() 129 | dialOptions := []grpc.DialOption{grpc.WithInsecure()} 130 | 131 | ctx, cancel := context.WithCancel(context.Background()) 132 | defer cancel() 133 | 134 | // in-process handler 135 | // err := pb.RegisterAuthServiceHandlerServer(ctx, mux, authServer) 136 | 137 | err := pb.RegisterAuthServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, dialOptions) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | // in-process handler 143 | // err = pb.RegisterLaptopServiceHandlerServer(ctx, mux, laptopServer) 144 | 145 | err = pb.RegisterLaptopServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, dialOptions) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | log.Printf("Start REST server at %s, TLS = %t", listener.Addr().String(), enableTLS) 151 | if enableTLS { 152 | return http.ServeTLS(listener, mux, serverCertFile, serverKeyFile) 153 | } 154 | return http.Serve(listener, mux) 155 | } 156 | 157 | func main() { 158 | port := flag.Int("port", 0, "the server port") 159 | enableTLS := flag.Bool("tls", false, "enable SSL/TLS") 160 | serverType := flag.String("type", "grpc", "type of server (grpc/rest)") 161 | endPoint := flag.String("endpoint", "", "gRPC endpoint") 162 | flag.Parse() 163 | 164 | userStore := service.NewInMemoryUserStore() 165 | err := seedUsers(userStore) 166 | if err != nil { 167 | log.Fatal("cannot seed users: ", err) 168 | } 169 | 170 | jwtManager := service.NewJWTManager(secretKey, tokenDuration) 171 | authServer := service.NewAuthServer(userStore, jwtManager) 172 | 173 | laptopStore := service.NewInMemoryLaptopStore() 174 | imageStore := service.NewDiskImageStore("img") 175 | ratingStore := service.NewInMemoryRatingStore() 176 | laptopServer := service.NewLaptopServer(laptopStore, imageStore, ratingStore) 177 | 178 | address := fmt.Sprintf("0.0.0.0:%d", *port) 179 | listener, err := net.Listen("tcp", address) 180 | if err != nil { 181 | log.Fatal("cannot start server: ", err) 182 | } 183 | 184 | if *serverType == "grpc" { 185 | err = runGRPCServer(authServer, laptopServer, jwtManager, *enableTLS, listener) 186 | } else { 187 | err = runRESTServer(authServer, laptopServer, jwtManager, *enableTLS, listener, *endPoint) 188 | } 189 | 190 | if err != nil { 191 | log.Fatal("cannot start server: ", err) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitlab.com/techschool/pcbook 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/golang/protobuf v1.4.3 8 | github.com/google/uuid v1.2.0 9 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0 10 | github.com/jinzhu/copier v0.2.3 11 | github.com/stretchr/testify v1.7.0 12 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 13 | google.golang.org/genproto v0.0.0-20210202153253-cf70463f6119 14 | google.golang.org/grpc v1.35.0 15 | google.golang.org/protobuf v1.25.0 16 | ) 17 | -------------------------------------------------------------------------------- /img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techschool/pcbook-go/aa74912a982ca43eb44163de853554a255f66c25/img/.gitkeep -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | error_log /usr/local/var/log/nginx/error.log; 4 | 5 | events { 6 | worker_connections 10; 7 | } 8 | 9 | http { 10 | access_log /usr/local/var/log/nginx/access.log; 11 | 12 | upstream auth_services { 13 | server 0.0.0.0:50051; 14 | server 0.0.0.0:50052; 15 | } 16 | 17 | upstream laptop_services { 18 | server 0.0.0.0:50051; 19 | server 0.0.0.0:50052; 20 | } 21 | 22 | server { 23 | listen 8080 ssl http2; 24 | 25 | # Mutual TLS between gRPC client and nginx 26 | ssl_certificate cert/server-cert.pem; 27 | ssl_certificate_key cert/server-key.pem; 28 | 29 | ssl_client_certificate cert/ca-cert.pem; 30 | ssl_verify_client on; 31 | 32 | location /techschool.pcbook.AuthService { 33 | grpc_pass grpcs://auth_services; 34 | 35 | # Mutual TLS between nginx and gRPC server 36 | grpc_ssl_certificate cert/server-cert.pem; 37 | grpc_ssl_certificate_key cert/server-key.pem; 38 | } 39 | 40 | location /techschool.pcbook.LaptopService { 41 | grpc_pass grpcs://laptop_services; 42 | 43 | # Mutual TLS between nginx and gRPC server 44 | grpc_ssl_certificate cert/server-cert.pem; 45 | grpc_ssl_certificate_key cert/server-key.pem; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pb/auth_service.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: auth_service.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | _ "google.golang.org/genproto/googleapis/api/annotations" 12 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 13 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 14 | reflect "reflect" 15 | sync "sync" 16 | ) 17 | 18 | const ( 19 | // Verify that this generated code is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 21 | // Verify that runtime/protoimpl is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 23 | ) 24 | 25 | // This is a compile-time assertion that a sufficiently up-to-date version 26 | // of the legacy proto package is being used. 27 | const _ = proto.ProtoPackageIsVersion4 28 | 29 | type LoginRequest struct { 30 | state protoimpl.MessageState 31 | sizeCache protoimpl.SizeCache 32 | unknownFields protoimpl.UnknownFields 33 | 34 | Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` 35 | Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` 36 | } 37 | 38 | func (x *LoginRequest) Reset() { 39 | *x = LoginRequest{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_auth_service_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *LoginRequest) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*LoginRequest) ProtoMessage() {} 52 | 53 | func (x *LoginRequest) ProtoReflect() protoreflect.Message { 54 | mi := &file_auth_service_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. 66 | func (*LoginRequest) Descriptor() ([]byte, []int) { 67 | return file_auth_service_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *LoginRequest) GetUsername() string { 71 | if x != nil { 72 | return x.Username 73 | } 74 | return "" 75 | } 76 | 77 | func (x *LoginRequest) GetPassword() string { 78 | if x != nil { 79 | return x.Password 80 | } 81 | return "" 82 | } 83 | 84 | type LoginResponse struct { 85 | state protoimpl.MessageState 86 | sizeCache protoimpl.SizeCache 87 | unknownFields protoimpl.UnknownFields 88 | 89 | AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` 90 | } 91 | 92 | func (x *LoginResponse) Reset() { 93 | *x = LoginResponse{} 94 | if protoimpl.UnsafeEnabled { 95 | mi := &file_auth_service_proto_msgTypes[1] 96 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 97 | ms.StoreMessageInfo(mi) 98 | } 99 | } 100 | 101 | func (x *LoginResponse) String() string { 102 | return protoimpl.X.MessageStringOf(x) 103 | } 104 | 105 | func (*LoginResponse) ProtoMessage() {} 106 | 107 | func (x *LoginResponse) ProtoReflect() protoreflect.Message { 108 | mi := &file_auth_service_proto_msgTypes[1] 109 | if protoimpl.UnsafeEnabled && x != nil { 110 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 111 | if ms.LoadMessageInfo() == nil { 112 | ms.StoreMessageInfo(mi) 113 | } 114 | return ms 115 | } 116 | return mi.MessageOf(x) 117 | } 118 | 119 | // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. 120 | func (*LoginResponse) Descriptor() ([]byte, []int) { 121 | return file_auth_service_proto_rawDescGZIP(), []int{1} 122 | } 123 | 124 | func (x *LoginResponse) GetAccessToken() string { 125 | if x != nil { 126 | return x.AccessToken 127 | } 128 | return "" 129 | } 130 | 131 | var File_auth_service_proto protoreflect.FileDescriptor 132 | 133 | var file_auth_service_proto_rawDesc = []byte{ 134 | 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 135 | 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 136 | 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 137 | 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 138 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x46, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 139 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 140 | 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 141 | 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 142 | 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x32, 0x0a, 143 | 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 144 | 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 145 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 146 | 0x6e, 0x32, 0x74, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 147 | 0x12, 0x65, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x63, 0x68, 148 | 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c, 0x6f, 149 | 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x74, 0x65, 0x63, 150 | 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c, 151 | 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 152 | 0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x6c, 153 | 0x6f, 0x67, 0x69, 0x6e, 0x3a, 0x01, 0x2a, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 154 | 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 155 | 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 156 | 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 157 | } 158 | 159 | var ( 160 | file_auth_service_proto_rawDescOnce sync.Once 161 | file_auth_service_proto_rawDescData = file_auth_service_proto_rawDesc 162 | ) 163 | 164 | func file_auth_service_proto_rawDescGZIP() []byte { 165 | file_auth_service_proto_rawDescOnce.Do(func() { 166 | file_auth_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_service_proto_rawDescData) 167 | }) 168 | return file_auth_service_proto_rawDescData 169 | } 170 | 171 | var file_auth_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 172 | var file_auth_service_proto_goTypes = []interface{}{ 173 | (*LoginRequest)(nil), // 0: techschool.pcbook.LoginRequest 174 | (*LoginResponse)(nil), // 1: techschool.pcbook.LoginResponse 175 | } 176 | var file_auth_service_proto_depIdxs = []int32{ 177 | 0, // 0: techschool.pcbook.AuthService.Login:input_type -> techschool.pcbook.LoginRequest 178 | 1, // 1: techschool.pcbook.AuthService.Login:output_type -> techschool.pcbook.LoginResponse 179 | 1, // [1:2] is the sub-list for method output_type 180 | 0, // [0:1] is the sub-list for method input_type 181 | 0, // [0:0] is the sub-list for extension type_name 182 | 0, // [0:0] is the sub-list for extension extendee 183 | 0, // [0:0] is the sub-list for field type_name 184 | } 185 | 186 | func init() { file_auth_service_proto_init() } 187 | func file_auth_service_proto_init() { 188 | if File_auth_service_proto != nil { 189 | return 190 | } 191 | if !protoimpl.UnsafeEnabled { 192 | file_auth_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 193 | switch v := v.(*LoginRequest); i { 194 | case 0: 195 | return &v.state 196 | case 1: 197 | return &v.sizeCache 198 | case 2: 199 | return &v.unknownFields 200 | default: 201 | return nil 202 | } 203 | } 204 | file_auth_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 205 | switch v := v.(*LoginResponse); i { 206 | case 0: 207 | return &v.state 208 | case 1: 209 | return &v.sizeCache 210 | case 2: 211 | return &v.unknownFields 212 | default: 213 | return nil 214 | } 215 | } 216 | } 217 | type x struct{} 218 | out := protoimpl.TypeBuilder{ 219 | File: protoimpl.DescBuilder{ 220 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 221 | RawDescriptor: file_auth_service_proto_rawDesc, 222 | NumEnums: 0, 223 | NumMessages: 2, 224 | NumExtensions: 0, 225 | NumServices: 1, 226 | }, 227 | GoTypes: file_auth_service_proto_goTypes, 228 | DependencyIndexes: file_auth_service_proto_depIdxs, 229 | MessageInfos: file_auth_service_proto_msgTypes, 230 | }.Build() 231 | File_auth_service_proto = out.File 232 | file_auth_service_proto_rawDesc = nil 233 | file_auth_service_proto_goTypes = nil 234 | file_auth_service_proto_depIdxs = nil 235 | } 236 | -------------------------------------------------------------------------------- /pb/auth_service.pb.gw.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. 2 | // source: auth_service.proto 3 | 4 | /* 5 | Package pb is a reverse proxy. 6 | 7 | It translates gRPC into RESTful JSON APIs. 8 | */ 9 | package pb 10 | 11 | import ( 12 | "context" 13 | "io" 14 | "net/http" 15 | 16 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 17 | "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" 18 | "google.golang.org/grpc" 19 | "google.golang.org/grpc/codes" 20 | "google.golang.org/grpc/grpclog" 21 | "google.golang.org/grpc/metadata" 22 | "google.golang.org/grpc/status" 23 | "google.golang.org/protobuf/proto" 24 | ) 25 | 26 | // Suppress "imported and not used" errors 27 | var _ codes.Code 28 | var _ io.Reader 29 | var _ status.Status 30 | var _ = runtime.String 31 | var _ = utilities.NewDoubleArray 32 | var _ = metadata.Join 33 | 34 | func request_AuthService_Login_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { 35 | var protoReq LoginRequest 36 | var metadata runtime.ServerMetadata 37 | 38 | newReader, berr := utilities.IOReaderFactory(req.Body) 39 | if berr != nil { 40 | return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) 41 | } 42 | if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { 43 | return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) 44 | } 45 | 46 | msg, err := client.Login(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) 47 | return msg, metadata, err 48 | 49 | } 50 | 51 | func local_request_AuthService_Login_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { 52 | var protoReq LoginRequest 53 | var metadata runtime.ServerMetadata 54 | 55 | newReader, berr := utilities.IOReaderFactory(req.Body) 56 | if berr != nil { 57 | return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) 58 | } 59 | if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { 60 | return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) 61 | } 62 | 63 | msg, err := server.Login(ctx, &protoReq) 64 | return msg, metadata, err 65 | 66 | } 67 | 68 | // RegisterAuthServiceHandlerServer registers the http handlers for service AuthService to "mux". 69 | // UnaryRPC :call AuthServiceServer directly. 70 | // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. 71 | // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthServiceHandlerFromEndpoint instead. 72 | func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer) error { 73 | 74 | mux.Handle("POST", pattern_AuthService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { 75 | ctx, cancel := context.WithCancel(req.Context()) 76 | defer cancel() 77 | var stream runtime.ServerTransportStream 78 | ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) 79 | inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 80 | rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/techschool.pcbook.AuthService/Login") 81 | if err != nil { 82 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 83 | return 84 | } 85 | resp, md, err := local_request_AuthService_Login_0(rctx, inboundMarshaler, server, req, pathParams) 86 | md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) 87 | ctx = runtime.NewServerMetadataContext(ctx, md) 88 | if err != nil { 89 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 90 | return 91 | } 92 | 93 | forward_AuthService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) 94 | 95 | }) 96 | 97 | return nil 98 | } 99 | 100 | // RegisterAuthServiceHandlerFromEndpoint is same as RegisterAuthServiceHandler but 101 | // automatically dials to "endpoint" and closes the connection when "ctx" gets done. 102 | func RegisterAuthServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { 103 | conn, err := grpc.Dial(endpoint, opts...) 104 | if err != nil { 105 | return err 106 | } 107 | defer func() { 108 | if err != nil { 109 | if cerr := conn.Close(); cerr != nil { 110 | grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) 111 | } 112 | return 113 | } 114 | go func() { 115 | <-ctx.Done() 116 | if cerr := conn.Close(); cerr != nil { 117 | grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) 118 | } 119 | }() 120 | }() 121 | 122 | return RegisterAuthServiceHandler(ctx, mux, conn) 123 | } 124 | 125 | // RegisterAuthServiceHandler registers the http handlers for service AuthService to "mux". 126 | // The handlers forward requests to the grpc endpoint over "conn". 127 | func RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { 128 | return RegisterAuthServiceHandlerClient(ctx, mux, NewAuthServiceClient(conn)) 129 | } 130 | 131 | // RegisterAuthServiceHandlerClient registers the http handlers for service AuthService 132 | // to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AuthServiceClient". 133 | // Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AuthServiceClient" 134 | // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in 135 | // "AuthServiceClient" to call the correct interceptors. 136 | func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthServiceClient) error { 137 | 138 | mux.Handle("POST", pattern_AuthService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { 139 | ctx, cancel := context.WithCancel(req.Context()) 140 | defer cancel() 141 | inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 142 | rctx, err := runtime.AnnotateContext(ctx, mux, req, "/techschool.pcbook.AuthService/Login") 143 | if err != nil { 144 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 145 | return 146 | } 147 | resp, md, err := request_AuthService_Login_0(rctx, inboundMarshaler, client, req, pathParams) 148 | ctx = runtime.NewServerMetadataContext(ctx, md) 149 | if err != nil { 150 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 151 | return 152 | } 153 | 154 | forward_AuthService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) 155 | 156 | }) 157 | 158 | return nil 159 | } 160 | 161 | var ( 162 | pattern_AuthService_Login_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "auth", "login"}, "")) 163 | ) 164 | 165 | var ( 166 | forward_AuthService_Login_0 = runtime.ForwardResponseMessage 167 | ) 168 | -------------------------------------------------------------------------------- /pb/auth_service_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package pb 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // AuthServiceClient is the client API for AuthService service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type AuthServiceClient interface { 21 | Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) 22 | } 23 | 24 | type authServiceClient struct { 25 | cc grpc.ClientConnInterface 26 | } 27 | 28 | func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { 29 | return &authServiceClient{cc} 30 | } 31 | 32 | func (c *authServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { 33 | out := new(LoginResponse) 34 | err := c.cc.Invoke(ctx, "/techschool.pcbook.AuthService/Login", in, out, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return out, nil 39 | } 40 | 41 | // AuthServiceServer is the server API for AuthService service. 42 | // All implementations must embed UnimplementedAuthServiceServer 43 | // for forward compatibility 44 | type AuthServiceServer interface { 45 | Login(context.Context, *LoginRequest) (*LoginResponse, error) 46 | mustEmbedUnimplementedAuthServiceServer() 47 | } 48 | 49 | // UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. 50 | type UnimplementedAuthServiceServer struct { 51 | } 52 | 53 | func (UnimplementedAuthServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { 54 | return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") 55 | } 56 | func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} 57 | 58 | // UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service. 59 | // Use of this interface is not recommended, as added methods to AuthServiceServer will 60 | // result in compilation errors. 61 | type UnsafeAuthServiceServer interface { 62 | mustEmbedUnimplementedAuthServiceServer() 63 | } 64 | 65 | func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { 66 | s.RegisterService(&AuthService_ServiceDesc, srv) 67 | } 68 | 69 | func _AuthService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 70 | in := new(LoginRequest) 71 | if err := dec(in); err != nil { 72 | return nil, err 73 | } 74 | if interceptor == nil { 75 | return srv.(AuthServiceServer).Login(ctx, in) 76 | } 77 | info := &grpc.UnaryServerInfo{ 78 | Server: srv, 79 | FullMethod: "/techschool.pcbook.AuthService/Login", 80 | } 81 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 82 | return srv.(AuthServiceServer).Login(ctx, req.(*LoginRequest)) 83 | } 84 | return interceptor(ctx, in, info, handler) 85 | } 86 | 87 | // AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. 88 | // It's only intended for direct use with grpc.RegisterService, 89 | // and not to be introspected or modified (even as a copy) 90 | var AuthService_ServiceDesc = grpc.ServiceDesc{ 91 | ServiceName: "techschool.pcbook.AuthService", 92 | HandlerType: (*AuthServiceServer)(nil), 93 | Methods: []grpc.MethodDesc{ 94 | { 95 | MethodName: "Login", 96 | Handler: _AuthService_Login_Handler, 97 | }, 98 | }, 99 | Streams: []grpc.StreamDesc{}, 100 | Metadata: "auth_service.proto", 101 | } 102 | -------------------------------------------------------------------------------- /pb/filter_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: filter_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type Filter struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | MaxPriceUsd float64 `protobuf:"fixed64,1,opt,name=max_price_usd,json=maxPriceUsd,proto3" json:"max_price_usd,omitempty"` 34 | MinCpuCores uint32 `protobuf:"varint,2,opt,name=min_cpu_cores,json=minCpuCores,proto3" json:"min_cpu_cores,omitempty"` 35 | MinCpuGhz float64 `protobuf:"fixed64,3,opt,name=min_cpu_ghz,json=minCpuGhz,proto3" json:"min_cpu_ghz,omitempty"` 36 | MinRam *Memory `protobuf:"bytes,4,opt,name=min_ram,json=minRam,proto3" json:"min_ram,omitempty"` 37 | } 38 | 39 | func (x *Filter) Reset() { 40 | *x = Filter{} 41 | if protoimpl.UnsafeEnabled { 42 | mi := &file_filter_message_proto_msgTypes[0] 43 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 44 | ms.StoreMessageInfo(mi) 45 | } 46 | } 47 | 48 | func (x *Filter) String() string { 49 | return protoimpl.X.MessageStringOf(x) 50 | } 51 | 52 | func (*Filter) ProtoMessage() {} 53 | 54 | func (x *Filter) ProtoReflect() protoreflect.Message { 55 | mi := &file_filter_message_proto_msgTypes[0] 56 | if protoimpl.UnsafeEnabled && x != nil { 57 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 58 | if ms.LoadMessageInfo() == nil { 59 | ms.StoreMessageInfo(mi) 60 | } 61 | return ms 62 | } 63 | return mi.MessageOf(x) 64 | } 65 | 66 | // Deprecated: Use Filter.ProtoReflect.Descriptor instead. 67 | func (*Filter) Descriptor() ([]byte, []int) { 68 | return file_filter_message_proto_rawDescGZIP(), []int{0} 69 | } 70 | 71 | func (x *Filter) GetMaxPriceUsd() float64 { 72 | if x != nil { 73 | return x.MaxPriceUsd 74 | } 75 | return 0 76 | } 77 | 78 | func (x *Filter) GetMinCpuCores() uint32 { 79 | if x != nil { 80 | return x.MinCpuCores 81 | } 82 | return 0 83 | } 84 | 85 | func (x *Filter) GetMinCpuGhz() float64 { 86 | if x != nil { 87 | return x.MinCpuGhz 88 | } 89 | return 0 90 | } 91 | 92 | func (x *Filter) GetMinRam() *Memory { 93 | if x != nil { 94 | return x.MinRam 95 | } 96 | return nil 97 | } 98 | 99 | var File_filter_message_proto protoreflect.FileDescriptor 100 | 101 | var file_filter_message_proto_rawDesc = []byte{ 102 | 0x0a, 0x14, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 103 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 104 | 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 105 | 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 106 | 0xa4, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 107 | 0x78, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 108 | 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x22, 109 | 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18, 110 | 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x43, 0x70, 0x75, 0x43, 0x6f, 0x72, 111 | 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x67, 0x68, 112 | 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x43, 0x70, 0x75, 0x47, 113 | 0x68, 0x7a, 0x12, 0x32, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x6d, 0x18, 0x04, 0x20, 114 | 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 115 | 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x06, 116 | 0x6d, 0x69, 0x6e, 0x52, 0x61, 0x6d, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 117 | 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 118 | 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 119 | 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 120 | } 121 | 122 | var ( 123 | file_filter_message_proto_rawDescOnce sync.Once 124 | file_filter_message_proto_rawDescData = file_filter_message_proto_rawDesc 125 | ) 126 | 127 | func file_filter_message_proto_rawDescGZIP() []byte { 128 | file_filter_message_proto_rawDescOnce.Do(func() { 129 | file_filter_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_filter_message_proto_rawDescData) 130 | }) 131 | return file_filter_message_proto_rawDescData 132 | } 133 | 134 | var file_filter_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 135 | var file_filter_message_proto_goTypes = []interface{}{ 136 | (*Filter)(nil), // 0: techschool.pcbook.Filter 137 | (*Memory)(nil), // 1: techschool.pcbook.Memory 138 | } 139 | var file_filter_message_proto_depIdxs = []int32{ 140 | 1, // 0: techschool.pcbook.Filter.min_ram:type_name -> techschool.pcbook.Memory 141 | 1, // [1:1] is the sub-list for method output_type 142 | 1, // [1:1] is the sub-list for method input_type 143 | 1, // [1:1] is the sub-list for extension type_name 144 | 1, // [1:1] is the sub-list for extension extendee 145 | 0, // [0:1] is the sub-list for field type_name 146 | } 147 | 148 | func init() { file_filter_message_proto_init() } 149 | func file_filter_message_proto_init() { 150 | if File_filter_message_proto != nil { 151 | return 152 | } 153 | file_memory_message_proto_init() 154 | if !protoimpl.UnsafeEnabled { 155 | file_filter_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 156 | switch v := v.(*Filter); i { 157 | case 0: 158 | return &v.state 159 | case 1: 160 | return &v.sizeCache 161 | case 2: 162 | return &v.unknownFields 163 | default: 164 | return nil 165 | } 166 | } 167 | } 168 | type x struct{} 169 | out := protoimpl.TypeBuilder{ 170 | File: protoimpl.DescBuilder{ 171 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 172 | RawDescriptor: file_filter_message_proto_rawDesc, 173 | NumEnums: 0, 174 | NumMessages: 1, 175 | NumExtensions: 0, 176 | NumServices: 0, 177 | }, 178 | GoTypes: file_filter_message_proto_goTypes, 179 | DependencyIndexes: file_filter_message_proto_depIdxs, 180 | MessageInfos: file_filter_message_proto_msgTypes, 181 | }.Build() 182 | File_filter_message_proto = out.File 183 | file_filter_message_proto_rawDesc = nil 184 | file_filter_message_proto_goTypes = nil 185 | file_filter_message_proto_depIdxs = nil 186 | } 187 | -------------------------------------------------------------------------------- /pb/keyboard_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: keyboard_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type Keyboard_Layout int32 29 | 30 | const ( 31 | Keyboard_UNKNOWN Keyboard_Layout = 0 32 | Keyboard_QWERTY Keyboard_Layout = 1 33 | Keyboard_QWERTZ Keyboard_Layout = 2 34 | Keyboard_AZERTY Keyboard_Layout = 3 35 | ) 36 | 37 | // Enum value maps for Keyboard_Layout. 38 | var ( 39 | Keyboard_Layout_name = map[int32]string{ 40 | 0: "UNKNOWN", 41 | 1: "QWERTY", 42 | 2: "QWERTZ", 43 | 3: "AZERTY", 44 | } 45 | Keyboard_Layout_value = map[string]int32{ 46 | "UNKNOWN": 0, 47 | "QWERTY": 1, 48 | "QWERTZ": 2, 49 | "AZERTY": 3, 50 | } 51 | ) 52 | 53 | func (x Keyboard_Layout) Enum() *Keyboard_Layout { 54 | p := new(Keyboard_Layout) 55 | *p = x 56 | return p 57 | } 58 | 59 | func (x Keyboard_Layout) String() string { 60 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 61 | } 62 | 63 | func (Keyboard_Layout) Descriptor() protoreflect.EnumDescriptor { 64 | return file_keyboard_message_proto_enumTypes[0].Descriptor() 65 | } 66 | 67 | func (Keyboard_Layout) Type() protoreflect.EnumType { 68 | return &file_keyboard_message_proto_enumTypes[0] 69 | } 70 | 71 | func (x Keyboard_Layout) Number() protoreflect.EnumNumber { 72 | return protoreflect.EnumNumber(x) 73 | } 74 | 75 | // Deprecated: Use Keyboard_Layout.Descriptor instead. 76 | func (Keyboard_Layout) EnumDescriptor() ([]byte, []int) { 77 | return file_keyboard_message_proto_rawDescGZIP(), []int{0, 0} 78 | } 79 | 80 | type Keyboard struct { 81 | state protoimpl.MessageState 82 | sizeCache protoimpl.SizeCache 83 | unknownFields protoimpl.UnknownFields 84 | 85 | Layout Keyboard_Layout `protobuf:"varint,1,opt,name=layout,proto3,enum=techschool.pcbook.Keyboard_Layout" json:"layout,omitempty"` 86 | Backlit bool `protobuf:"varint,2,opt,name=backlit,proto3" json:"backlit,omitempty"` 87 | } 88 | 89 | func (x *Keyboard) Reset() { 90 | *x = Keyboard{} 91 | if protoimpl.UnsafeEnabled { 92 | mi := &file_keyboard_message_proto_msgTypes[0] 93 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 94 | ms.StoreMessageInfo(mi) 95 | } 96 | } 97 | 98 | func (x *Keyboard) String() string { 99 | return protoimpl.X.MessageStringOf(x) 100 | } 101 | 102 | func (*Keyboard) ProtoMessage() {} 103 | 104 | func (x *Keyboard) ProtoReflect() protoreflect.Message { 105 | mi := &file_keyboard_message_proto_msgTypes[0] 106 | if protoimpl.UnsafeEnabled && x != nil { 107 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 108 | if ms.LoadMessageInfo() == nil { 109 | ms.StoreMessageInfo(mi) 110 | } 111 | return ms 112 | } 113 | return mi.MessageOf(x) 114 | } 115 | 116 | // Deprecated: Use Keyboard.ProtoReflect.Descriptor instead. 117 | func (*Keyboard) Descriptor() ([]byte, []int) { 118 | return file_keyboard_message_proto_rawDescGZIP(), []int{0} 119 | } 120 | 121 | func (x *Keyboard) GetLayout() Keyboard_Layout { 122 | if x != nil { 123 | return x.Layout 124 | } 125 | return Keyboard_UNKNOWN 126 | } 127 | 128 | func (x *Keyboard) GetBacklit() bool { 129 | if x != nil { 130 | return x.Backlit 131 | } 132 | return false 133 | } 134 | 135 | var File_keyboard_message_proto protoreflect.FileDescriptor 136 | 137 | var file_keyboard_message_proto_rawDesc = []byte{ 138 | 0x0a, 0x16, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 139 | 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 140 | 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0x9b, 0x01, 0x0a, 0x08, 141 | 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x79, 0x6f, 142 | 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 143 | 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4b, 0x65, 0x79, 144 | 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x52, 0x06, 0x6c, 0x61, 145 | 0x79, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x74, 0x18, 146 | 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x74, 0x22, 0x39, 147 | 0x0a, 0x06, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 148 | 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x10, 149 | 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x57, 0x45, 0x52, 0x54, 0x5a, 0x10, 0x02, 0x12, 0x0a, 0x0a, 150 | 0x06, 0x41, 0x5a, 0x45, 0x52, 0x54, 0x59, 0x10, 0x03, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 151 | 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 152 | 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 153 | 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 154 | } 155 | 156 | var ( 157 | file_keyboard_message_proto_rawDescOnce sync.Once 158 | file_keyboard_message_proto_rawDescData = file_keyboard_message_proto_rawDesc 159 | ) 160 | 161 | func file_keyboard_message_proto_rawDescGZIP() []byte { 162 | file_keyboard_message_proto_rawDescOnce.Do(func() { 163 | file_keyboard_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_keyboard_message_proto_rawDescData) 164 | }) 165 | return file_keyboard_message_proto_rawDescData 166 | } 167 | 168 | var file_keyboard_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 169 | var file_keyboard_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 170 | var file_keyboard_message_proto_goTypes = []interface{}{ 171 | (Keyboard_Layout)(0), // 0: techschool.pcbook.Keyboard.Layout 172 | (*Keyboard)(nil), // 1: techschool.pcbook.Keyboard 173 | } 174 | var file_keyboard_message_proto_depIdxs = []int32{ 175 | 0, // 0: techschool.pcbook.Keyboard.layout:type_name -> techschool.pcbook.Keyboard.Layout 176 | 1, // [1:1] is the sub-list for method output_type 177 | 1, // [1:1] is the sub-list for method input_type 178 | 1, // [1:1] is the sub-list for extension type_name 179 | 1, // [1:1] is the sub-list for extension extendee 180 | 0, // [0:1] is the sub-list for field type_name 181 | } 182 | 183 | func init() { file_keyboard_message_proto_init() } 184 | func file_keyboard_message_proto_init() { 185 | if File_keyboard_message_proto != nil { 186 | return 187 | } 188 | if !protoimpl.UnsafeEnabled { 189 | file_keyboard_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 190 | switch v := v.(*Keyboard); i { 191 | case 0: 192 | return &v.state 193 | case 1: 194 | return &v.sizeCache 195 | case 2: 196 | return &v.unknownFields 197 | default: 198 | return nil 199 | } 200 | } 201 | } 202 | type x struct{} 203 | out := protoimpl.TypeBuilder{ 204 | File: protoimpl.DescBuilder{ 205 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 206 | RawDescriptor: file_keyboard_message_proto_rawDesc, 207 | NumEnums: 1, 208 | NumMessages: 1, 209 | NumExtensions: 0, 210 | NumServices: 0, 211 | }, 212 | GoTypes: file_keyboard_message_proto_goTypes, 213 | DependencyIndexes: file_keyboard_message_proto_depIdxs, 214 | EnumInfos: file_keyboard_message_proto_enumTypes, 215 | MessageInfos: file_keyboard_message_proto_msgTypes, 216 | }.Build() 217 | File_keyboard_message_proto = out.File 218 | file_keyboard_message_proto_rawDesc = nil 219 | file_keyboard_message_proto_goTypes = nil 220 | file_keyboard_message_proto_depIdxs = nil 221 | } 222 | -------------------------------------------------------------------------------- /pb/laptop_service_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package pb 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // LaptopServiceClient is the client API for LaptopService service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type LaptopServiceClient interface { 21 | CreateLaptop(ctx context.Context, in *CreateLaptopRequest, opts ...grpc.CallOption) (*CreateLaptopResponse, error) 22 | SearchLaptop(ctx context.Context, in *SearchLaptopRequest, opts ...grpc.CallOption) (LaptopService_SearchLaptopClient, error) 23 | UploadImage(ctx context.Context, opts ...grpc.CallOption) (LaptopService_UploadImageClient, error) 24 | RateLaptop(ctx context.Context, opts ...grpc.CallOption) (LaptopService_RateLaptopClient, error) 25 | } 26 | 27 | type laptopServiceClient struct { 28 | cc grpc.ClientConnInterface 29 | } 30 | 31 | func NewLaptopServiceClient(cc grpc.ClientConnInterface) LaptopServiceClient { 32 | return &laptopServiceClient{cc} 33 | } 34 | 35 | func (c *laptopServiceClient) CreateLaptop(ctx context.Context, in *CreateLaptopRequest, opts ...grpc.CallOption) (*CreateLaptopResponse, error) { 36 | out := new(CreateLaptopResponse) 37 | err := c.cc.Invoke(ctx, "/techschool.pcbook.LaptopService/CreateLaptop", in, out, opts...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return out, nil 42 | } 43 | 44 | func (c *laptopServiceClient) SearchLaptop(ctx context.Context, in *SearchLaptopRequest, opts ...grpc.CallOption) (LaptopService_SearchLaptopClient, error) { 45 | stream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[0], "/techschool.pcbook.LaptopService/SearchLaptop", opts...) 46 | if err != nil { 47 | return nil, err 48 | } 49 | x := &laptopServiceSearchLaptopClient{stream} 50 | if err := x.ClientStream.SendMsg(in); err != nil { 51 | return nil, err 52 | } 53 | if err := x.ClientStream.CloseSend(); err != nil { 54 | return nil, err 55 | } 56 | return x, nil 57 | } 58 | 59 | type LaptopService_SearchLaptopClient interface { 60 | Recv() (*SearchLaptopResponse, error) 61 | grpc.ClientStream 62 | } 63 | 64 | type laptopServiceSearchLaptopClient struct { 65 | grpc.ClientStream 66 | } 67 | 68 | func (x *laptopServiceSearchLaptopClient) Recv() (*SearchLaptopResponse, error) { 69 | m := new(SearchLaptopResponse) 70 | if err := x.ClientStream.RecvMsg(m); err != nil { 71 | return nil, err 72 | } 73 | return m, nil 74 | } 75 | 76 | func (c *laptopServiceClient) UploadImage(ctx context.Context, opts ...grpc.CallOption) (LaptopService_UploadImageClient, error) { 77 | stream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[1], "/techschool.pcbook.LaptopService/UploadImage", opts...) 78 | if err != nil { 79 | return nil, err 80 | } 81 | x := &laptopServiceUploadImageClient{stream} 82 | return x, nil 83 | } 84 | 85 | type LaptopService_UploadImageClient interface { 86 | Send(*UploadImageRequest) error 87 | CloseAndRecv() (*UploadImageResponse, error) 88 | grpc.ClientStream 89 | } 90 | 91 | type laptopServiceUploadImageClient struct { 92 | grpc.ClientStream 93 | } 94 | 95 | func (x *laptopServiceUploadImageClient) Send(m *UploadImageRequest) error { 96 | return x.ClientStream.SendMsg(m) 97 | } 98 | 99 | func (x *laptopServiceUploadImageClient) CloseAndRecv() (*UploadImageResponse, error) { 100 | if err := x.ClientStream.CloseSend(); err != nil { 101 | return nil, err 102 | } 103 | m := new(UploadImageResponse) 104 | if err := x.ClientStream.RecvMsg(m); err != nil { 105 | return nil, err 106 | } 107 | return m, nil 108 | } 109 | 110 | func (c *laptopServiceClient) RateLaptop(ctx context.Context, opts ...grpc.CallOption) (LaptopService_RateLaptopClient, error) { 111 | stream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[2], "/techschool.pcbook.LaptopService/RateLaptop", opts...) 112 | if err != nil { 113 | return nil, err 114 | } 115 | x := &laptopServiceRateLaptopClient{stream} 116 | return x, nil 117 | } 118 | 119 | type LaptopService_RateLaptopClient interface { 120 | Send(*RateLaptopRequest) error 121 | Recv() (*RateLaptopResponse, error) 122 | grpc.ClientStream 123 | } 124 | 125 | type laptopServiceRateLaptopClient struct { 126 | grpc.ClientStream 127 | } 128 | 129 | func (x *laptopServiceRateLaptopClient) Send(m *RateLaptopRequest) error { 130 | return x.ClientStream.SendMsg(m) 131 | } 132 | 133 | func (x *laptopServiceRateLaptopClient) Recv() (*RateLaptopResponse, error) { 134 | m := new(RateLaptopResponse) 135 | if err := x.ClientStream.RecvMsg(m); err != nil { 136 | return nil, err 137 | } 138 | return m, nil 139 | } 140 | 141 | // LaptopServiceServer is the server API for LaptopService service. 142 | // All implementations must embed UnimplementedLaptopServiceServer 143 | // for forward compatibility 144 | type LaptopServiceServer interface { 145 | CreateLaptop(context.Context, *CreateLaptopRequest) (*CreateLaptopResponse, error) 146 | SearchLaptop(*SearchLaptopRequest, LaptopService_SearchLaptopServer) error 147 | UploadImage(LaptopService_UploadImageServer) error 148 | RateLaptop(LaptopService_RateLaptopServer) error 149 | mustEmbedUnimplementedLaptopServiceServer() 150 | } 151 | 152 | // UnimplementedLaptopServiceServer must be embedded to have forward compatible implementations. 153 | type UnimplementedLaptopServiceServer struct { 154 | } 155 | 156 | func (UnimplementedLaptopServiceServer) CreateLaptop(context.Context, *CreateLaptopRequest) (*CreateLaptopResponse, error) { 157 | return nil, status.Errorf(codes.Unimplemented, "method CreateLaptop not implemented") 158 | } 159 | func (UnimplementedLaptopServiceServer) SearchLaptop(*SearchLaptopRequest, LaptopService_SearchLaptopServer) error { 160 | return status.Errorf(codes.Unimplemented, "method SearchLaptop not implemented") 161 | } 162 | func (UnimplementedLaptopServiceServer) UploadImage(LaptopService_UploadImageServer) error { 163 | return status.Errorf(codes.Unimplemented, "method UploadImage not implemented") 164 | } 165 | func (UnimplementedLaptopServiceServer) RateLaptop(LaptopService_RateLaptopServer) error { 166 | return status.Errorf(codes.Unimplemented, "method RateLaptop not implemented") 167 | } 168 | func (UnimplementedLaptopServiceServer) mustEmbedUnimplementedLaptopServiceServer() {} 169 | 170 | // UnsafeLaptopServiceServer may be embedded to opt out of forward compatibility for this service. 171 | // Use of this interface is not recommended, as added methods to LaptopServiceServer will 172 | // result in compilation errors. 173 | type UnsafeLaptopServiceServer interface { 174 | mustEmbedUnimplementedLaptopServiceServer() 175 | } 176 | 177 | func RegisterLaptopServiceServer(s grpc.ServiceRegistrar, srv LaptopServiceServer) { 178 | s.RegisterService(&LaptopService_ServiceDesc, srv) 179 | } 180 | 181 | func _LaptopService_CreateLaptop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 182 | in := new(CreateLaptopRequest) 183 | if err := dec(in); err != nil { 184 | return nil, err 185 | } 186 | if interceptor == nil { 187 | return srv.(LaptopServiceServer).CreateLaptop(ctx, in) 188 | } 189 | info := &grpc.UnaryServerInfo{ 190 | Server: srv, 191 | FullMethod: "/techschool.pcbook.LaptopService/CreateLaptop", 192 | } 193 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 194 | return srv.(LaptopServiceServer).CreateLaptop(ctx, req.(*CreateLaptopRequest)) 195 | } 196 | return interceptor(ctx, in, info, handler) 197 | } 198 | 199 | func _LaptopService_SearchLaptop_Handler(srv interface{}, stream grpc.ServerStream) error { 200 | m := new(SearchLaptopRequest) 201 | if err := stream.RecvMsg(m); err != nil { 202 | return err 203 | } 204 | return srv.(LaptopServiceServer).SearchLaptop(m, &laptopServiceSearchLaptopServer{stream}) 205 | } 206 | 207 | type LaptopService_SearchLaptopServer interface { 208 | Send(*SearchLaptopResponse) error 209 | grpc.ServerStream 210 | } 211 | 212 | type laptopServiceSearchLaptopServer struct { 213 | grpc.ServerStream 214 | } 215 | 216 | func (x *laptopServiceSearchLaptopServer) Send(m *SearchLaptopResponse) error { 217 | return x.ServerStream.SendMsg(m) 218 | } 219 | 220 | func _LaptopService_UploadImage_Handler(srv interface{}, stream grpc.ServerStream) error { 221 | return srv.(LaptopServiceServer).UploadImage(&laptopServiceUploadImageServer{stream}) 222 | } 223 | 224 | type LaptopService_UploadImageServer interface { 225 | SendAndClose(*UploadImageResponse) error 226 | Recv() (*UploadImageRequest, error) 227 | grpc.ServerStream 228 | } 229 | 230 | type laptopServiceUploadImageServer struct { 231 | grpc.ServerStream 232 | } 233 | 234 | func (x *laptopServiceUploadImageServer) SendAndClose(m *UploadImageResponse) error { 235 | return x.ServerStream.SendMsg(m) 236 | } 237 | 238 | func (x *laptopServiceUploadImageServer) Recv() (*UploadImageRequest, error) { 239 | m := new(UploadImageRequest) 240 | if err := x.ServerStream.RecvMsg(m); err != nil { 241 | return nil, err 242 | } 243 | return m, nil 244 | } 245 | 246 | func _LaptopService_RateLaptop_Handler(srv interface{}, stream grpc.ServerStream) error { 247 | return srv.(LaptopServiceServer).RateLaptop(&laptopServiceRateLaptopServer{stream}) 248 | } 249 | 250 | type LaptopService_RateLaptopServer interface { 251 | Send(*RateLaptopResponse) error 252 | Recv() (*RateLaptopRequest, error) 253 | grpc.ServerStream 254 | } 255 | 256 | type laptopServiceRateLaptopServer struct { 257 | grpc.ServerStream 258 | } 259 | 260 | func (x *laptopServiceRateLaptopServer) Send(m *RateLaptopResponse) error { 261 | return x.ServerStream.SendMsg(m) 262 | } 263 | 264 | func (x *laptopServiceRateLaptopServer) Recv() (*RateLaptopRequest, error) { 265 | m := new(RateLaptopRequest) 266 | if err := x.ServerStream.RecvMsg(m); err != nil { 267 | return nil, err 268 | } 269 | return m, nil 270 | } 271 | 272 | // LaptopService_ServiceDesc is the grpc.ServiceDesc for LaptopService service. 273 | // It's only intended for direct use with grpc.RegisterService, 274 | // and not to be introspected or modified (even as a copy) 275 | var LaptopService_ServiceDesc = grpc.ServiceDesc{ 276 | ServiceName: "techschool.pcbook.LaptopService", 277 | HandlerType: (*LaptopServiceServer)(nil), 278 | Methods: []grpc.MethodDesc{ 279 | { 280 | MethodName: "CreateLaptop", 281 | Handler: _LaptopService_CreateLaptop_Handler, 282 | }, 283 | }, 284 | Streams: []grpc.StreamDesc{ 285 | { 286 | StreamName: "SearchLaptop", 287 | Handler: _LaptopService_SearchLaptop_Handler, 288 | ServerStreams: true, 289 | }, 290 | { 291 | StreamName: "UploadImage", 292 | Handler: _LaptopService_UploadImage_Handler, 293 | ClientStreams: true, 294 | }, 295 | { 296 | StreamName: "RateLaptop", 297 | Handler: _LaptopService_RateLaptop_Handler, 298 | ServerStreams: true, 299 | ClientStreams: true, 300 | }, 301 | }, 302 | Metadata: "laptop_service.proto", 303 | } 304 | -------------------------------------------------------------------------------- /pb/memory_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: memory_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type Memory_Unit int32 29 | 30 | const ( 31 | Memory_UNKNOWN Memory_Unit = 0 32 | Memory_BIT Memory_Unit = 1 33 | Memory_BYTE Memory_Unit = 2 34 | Memory_KILOBYTE Memory_Unit = 3 35 | Memory_MEGABYTE Memory_Unit = 4 36 | Memory_GIGABYTE Memory_Unit = 5 37 | Memory_TERABYTE Memory_Unit = 6 38 | ) 39 | 40 | // Enum value maps for Memory_Unit. 41 | var ( 42 | Memory_Unit_name = map[int32]string{ 43 | 0: "UNKNOWN", 44 | 1: "BIT", 45 | 2: "BYTE", 46 | 3: "KILOBYTE", 47 | 4: "MEGABYTE", 48 | 5: "GIGABYTE", 49 | 6: "TERABYTE", 50 | } 51 | Memory_Unit_value = map[string]int32{ 52 | "UNKNOWN": 0, 53 | "BIT": 1, 54 | "BYTE": 2, 55 | "KILOBYTE": 3, 56 | "MEGABYTE": 4, 57 | "GIGABYTE": 5, 58 | "TERABYTE": 6, 59 | } 60 | ) 61 | 62 | func (x Memory_Unit) Enum() *Memory_Unit { 63 | p := new(Memory_Unit) 64 | *p = x 65 | return p 66 | } 67 | 68 | func (x Memory_Unit) String() string { 69 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 70 | } 71 | 72 | func (Memory_Unit) Descriptor() protoreflect.EnumDescriptor { 73 | return file_memory_message_proto_enumTypes[0].Descriptor() 74 | } 75 | 76 | func (Memory_Unit) Type() protoreflect.EnumType { 77 | return &file_memory_message_proto_enumTypes[0] 78 | } 79 | 80 | func (x Memory_Unit) Number() protoreflect.EnumNumber { 81 | return protoreflect.EnumNumber(x) 82 | } 83 | 84 | // Deprecated: Use Memory_Unit.Descriptor instead. 85 | func (Memory_Unit) EnumDescriptor() ([]byte, []int) { 86 | return file_memory_message_proto_rawDescGZIP(), []int{0, 0} 87 | } 88 | 89 | type Memory struct { 90 | state protoimpl.MessageState 91 | sizeCache protoimpl.SizeCache 92 | unknownFields protoimpl.UnknownFields 93 | 94 | Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` 95 | Unit Memory_Unit `protobuf:"varint,2,opt,name=unit,proto3,enum=techschool.pcbook.Memory_Unit" json:"unit,omitempty"` 96 | } 97 | 98 | func (x *Memory) Reset() { 99 | *x = Memory{} 100 | if protoimpl.UnsafeEnabled { 101 | mi := &file_memory_message_proto_msgTypes[0] 102 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 103 | ms.StoreMessageInfo(mi) 104 | } 105 | } 106 | 107 | func (x *Memory) String() string { 108 | return protoimpl.X.MessageStringOf(x) 109 | } 110 | 111 | func (*Memory) ProtoMessage() {} 112 | 113 | func (x *Memory) ProtoReflect() protoreflect.Message { 114 | mi := &file_memory_message_proto_msgTypes[0] 115 | if protoimpl.UnsafeEnabled && x != nil { 116 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 117 | if ms.LoadMessageInfo() == nil { 118 | ms.StoreMessageInfo(mi) 119 | } 120 | return ms 121 | } 122 | return mi.MessageOf(x) 123 | } 124 | 125 | // Deprecated: Use Memory.ProtoReflect.Descriptor instead. 126 | func (*Memory) Descriptor() ([]byte, []int) { 127 | return file_memory_message_proto_rawDescGZIP(), []int{0} 128 | } 129 | 130 | func (x *Memory) GetValue() uint64 { 131 | if x != nil { 132 | return x.Value 133 | } 134 | return 0 135 | } 136 | 137 | func (x *Memory) GetUnit() Memory_Unit { 138 | if x != nil { 139 | return x.Unit 140 | } 141 | return Memory_UNKNOWN 142 | } 143 | 144 | var File_memory_message_proto protoreflect.FileDescriptor 145 | 146 | var file_memory_message_proto_rawDesc = []byte{ 147 | 0x0a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 148 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 149 | 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0xb2, 0x01, 0x0a, 0x06, 0x4d, 0x65, 150 | 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 151 | 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x75, 0x6e, 152 | 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 153 | 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 154 | 0x6f, 0x72, 0x79, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x22, 0x5e, 155 | 0x0a, 0x04, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 156 | 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x42, 0x49, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 157 | 0x42, 0x59, 0x54, 0x45, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4b, 0x49, 0x4c, 0x4f, 0x42, 0x59, 158 | 0x54, 0x45, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x45, 0x47, 0x41, 0x42, 0x59, 0x54, 0x45, 159 | 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x47, 0x49, 0x47, 0x41, 0x42, 0x59, 0x54, 0x45, 0x10, 0x05, 160 | 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x45, 0x52, 0x41, 0x42, 0x59, 0x54, 0x45, 0x10, 0x06, 0x42, 0x29, 161 | 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 162 | 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 163 | 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 164 | 0x33, 165 | } 166 | 167 | var ( 168 | file_memory_message_proto_rawDescOnce sync.Once 169 | file_memory_message_proto_rawDescData = file_memory_message_proto_rawDesc 170 | ) 171 | 172 | func file_memory_message_proto_rawDescGZIP() []byte { 173 | file_memory_message_proto_rawDescOnce.Do(func() { 174 | file_memory_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_memory_message_proto_rawDescData) 175 | }) 176 | return file_memory_message_proto_rawDescData 177 | } 178 | 179 | var file_memory_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 180 | var file_memory_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 181 | var file_memory_message_proto_goTypes = []interface{}{ 182 | (Memory_Unit)(0), // 0: techschool.pcbook.Memory.Unit 183 | (*Memory)(nil), // 1: techschool.pcbook.Memory 184 | } 185 | var file_memory_message_proto_depIdxs = []int32{ 186 | 0, // 0: techschool.pcbook.Memory.unit:type_name -> techschool.pcbook.Memory.Unit 187 | 1, // [1:1] is the sub-list for method output_type 188 | 1, // [1:1] is the sub-list for method input_type 189 | 1, // [1:1] is the sub-list for extension type_name 190 | 1, // [1:1] is the sub-list for extension extendee 191 | 0, // [0:1] is the sub-list for field type_name 192 | } 193 | 194 | func init() { file_memory_message_proto_init() } 195 | func file_memory_message_proto_init() { 196 | if File_memory_message_proto != nil { 197 | return 198 | } 199 | if !protoimpl.UnsafeEnabled { 200 | file_memory_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 201 | switch v := v.(*Memory); i { 202 | case 0: 203 | return &v.state 204 | case 1: 205 | return &v.sizeCache 206 | case 2: 207 | return &v.unknownFields 208 | default: 209 | return nil 210 | } 211 | } 212 | } 213 | type x struct{} 214 | out := protoimpl.TypeBuilder{ 215 | File: protoimpl.DescBuilder{ 216 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 217 | RawDescriptor: file_memory_message_proto_rawDesc, 218 | NumEnums: 1, 219 | NumMessages: 1, 220 | NumExtensions: 0, 221 | NumServices: 0, 222 | }, 223 | GoTypes: file_memory_message_proto_goTypes, 224 | DependencyIndexes: file_memory_message_proto_depIdxs, 225 | EnumInfos: file_memory_message_proto_enumTypes, 226 | MessageInfos: file_memory_message_proto_msgTypes, 227 | }.Build() 228 | File_memory_message_proto = out.File 229 | file_memory_message_proto_rawDesc = nil 230 | file_memory_message_proto_goTypes = nil 231 | file_memory_message_proto_depIdxs = nil 232 | } 233 | -------------------------------------------------------------------------------- /pb/processor_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: processor_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type CPU struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Brand string `protobuf:"bytes,1,opt,name=brand,proto3" json:"brand,omitempty"` 34 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 35 | NumberCores uint32 `protobuf:"varint,3,opt,name=number_cores,json=numberCores,proto3" json:"number_cores,omitempty"` 36 | NumberThreads uint32 `protobuf:"varint,4,opt,name=number_threads,json=numberThreads,proto3" json:"number_threads,omitempty"` 37 | MinGhz float64 `protobuf:"fixed64,5,opt,name=min_ghz,json=minGhz,proto3" json:"min_ghz,omitempty"` 38 | MaxGhz float64 `protobuf:"fixed64,6,opt,name=max_ghz,json=maxGhz,proto3" json:"max_ghz,omitempty"` 39 | } 40 | 41 | func (x *CPU) Reset() { 42 | *x = CPU{} 43 | if protoimpl.UnsafeEnabled { 44 | mi := &file_processor_message_proto_msgTypes[0] 45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 46 | ms.StoreMessageInfo(mi) 47 | } 48 | } 49 | 50 | func (x *CPU) String() string { 51 | return protoimpl.X.MessageStringOf(x) 52 | } 53 | 54 | func (*CPU) ProtoMessage() {} 55 | 56 | func (x *CPU) ProtoReflect() protoreflect.Message { 57 | mi := &file_processor_message_proto_msgTypes[0] 58 | if protoimpl.UnsafeEnabled && x != nil { 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | if ms.LoadMessageInfo() == nil { 61 | ms.StoreMessageInfo(mi) 62 | } 63 | return ms 64 | } 65 | return mi.MessageOf(x) 66 | } 67 | 68 | // Deprecated: Use CPU.ProtoReflect.Descriptor instead. 69 | func (*CPU) Descriptor() ([]byte, []int) { 70 | return file_processor_message_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | func (x *CPU) GetBrand() string { 74 | if x != nil { 75 | return x.Brand 76 | } 77 | return "" 78 | } 79 | 80 | func (x *CPU) GetName() string { 81 | if x != nil { 82 | return x.Name 83 | } 84 | return "" 85 | } 86 | 87 | func (x *CPU) GetNumberCores() uint32 { 88 | if x != nil { 89 | return x.NumberCores 90 | } 91 | return 0 92 | } 93 | 94 | func (x *CPU) GetNumberThreads() uint32 { 95 | if x != nil { 96 | return x.NumberThreads 97 | } 98 | return 0 99 | } 100 | 101 | func (x *CPU) GetMinGhz() float64 { 102 | if x != nil { 103 | return x.MinGhz 104 | } 105 | return 0 106 | } 107 | 108 | func (x *CPU) GetMaxGhz() float64 { 109 | if x != nil { 110 | return x.MaxGhz 111 | } 112 | return 0 113 | } 114 | 115 | type GPU struct { 116 | state protoimpl.MessageState 117 | sizeCache protoimpl.SizeCache 118 | unknownFields protoimpl.UnknownFields 119 | 120 | Brand string `protobuf:"bytes,1,opt,name=brand,proto3" json:"brand,omitempty"` 121 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 122 | MinGhz float64 `protobuf:"fixed64,3,opt,name=min_ghz,json=minGhz,proto3" json:"min_ghz,omitempty"` 123 | MaxGhz float64 `protobuf:"fixed64,4,opt,name=max_ghz,json=maxGhz,proto3" json:"max_ghz,omitempty"` 124 | Memory *Memory `protobuf:"bytes,5,opt,name=memory,proto3" json:"memory,omitempty"` 125 | } 126 | 127 | func (x *GPU) Reset() { 128 | *x = GPU{} 129 | if protoimpl.UnsafeEnabled { 130 | mi := &file_processor_message_proto_msgTypes[1] 131 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 132 | ms.StoreMessageInfo(mi) 133 | } 134 | } 135 | 136 | func (x *GPU) String() string { 137 | return protoimpl.X.MessageStringOf(x) 138 | } 139 | 140 | func (*GPU) ProtoMessage() {} 141 | 142 | func (x *GPU) ProtoReflect() protoreflect.Message { 143 | mi := &file_processor_message_proto_msgTypes[1] 144 | if protoimpl.UnsafeEnabled && x != nil { 145 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 146 | if ms.LoadMessageInfo() == nil { 147 | ms.StoreMessageInfo(mi) 148 | } 149 | return ms 150 | } 151 | return mi.MessageOf(x) 152 | } 153 | 154 | // Deprecated: Use GPU.ProtoReflect.Descriptor instead. 155 | func (*GPU) Descriptor() ([]byte, []int) { 156 | return file_processor_message_proto_rawDescGZIP(), []int{1} 157 | } 158 | 159 | func (x *GPU) GetBrand() string { 160 | if x != nil { 161 | return x.Brand 162 | } 163 | return "" 164 | } 165 | 166 | func (x *GPU) GetName() string { 167 | if x != nil { 168 | return x.Name 169 | } 170 | return "" 171 | } 172 | 173 | func (x *GPU) GetMinGhz() float64 { 174 | if x != nil { 175 | return x.MinGhz 176 | } 177 | return 0 178 | } 179 | 180 | func (x *GPU) GetMaxGhz() float64 { 181 | if x != nil { 182 | return x.MaxGhz 183 | } 184 | return 0 185 | } 186 | 187 | func (x *GPU) GetMemory() *Memory { 188 | if x != nil { 189 | return x.Memory 190 | } 191 | return nil 192 | } 193 | 194 | var File_processor_message_proto protoreflect.FileDescriptor 195 | 196 | var file_processor_message_proto_rawDesc = []byte{ 197 | 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 198 | 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 199 | 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65, 200 | 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 201 | 0x74, 0x6f, 0x22, 0xab, 0x01, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 202 | 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 203 | 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 204 | 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x63, 205 | 0x6f, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x62, 206 | 0x65, 0x72, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x62, 0x65, 207 | 0x72, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 208 | 0x0d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x12, 0x17, 209 | 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 210 | 0x06, 0x6d, 0x69, 0x6e, 0x47, 0x68, 0x7a, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x67, 211 | 0x68, 0x7a, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x47, 0x68, 0x7a, 212 | 0x22, 0x94, 0x01, 0x0a, 0x03, 0x47, 0x50, 0x55, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 0x61, 0x6e, 213 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x12, 0x12, 214 | 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 215 | 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x03, 0x20, 216 | 0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x69, 0x6e, 0x47, 0x68, 0x7a, 0x12, 0x17, 0x0a, 0x07, 0x6d, 217 | 0x61, 0x78, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x61, 218 | 0x78, 0x47, 0x68, 0x7a, 0x12, 0x31, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x05, 219 | 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 220 | 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 221 | 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 222 | 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 223 | 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 224 | 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 225 | } 226 | 227 | var ( 228 | file_processor_message_proto_rawDescOnce sync.Once 229 | file_processor_message_proto_rawDescData = file_processor_message_proto_rawDesc 230 | ) 231 | 232 | func file_processor_message_proto_rawDescGZIP() []byte { 233 | file_processor_message_proto_rawDescOnce.Do(func() { 234 | file_processor_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_processor_message_proto_rawDescData) 235 | }) 236 | return file_processor_message_proto_rawDescData 237 | } 238 | 239 | var file_processor_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 240 | var file_processor_message_proto_goTypes = []interface{}{ 241 | (*CPU)(nil), // 0: techschool.pcbook.CPU 242 | (*GPU)(nil), // 1: techschool.pcbook.GPU 243 | (*Memory)(nil), // 2: techschool.pcbook.Memory 244 | } 245 | var file_processor_message_proto_depIdxs = []int32{ 246 | 2, // 0: techschool.pcbook.GPU.memory:type_name -> techschool.pcbook.Memory 247 | 1, // [1:1] is the sub-list for method output_type 248 | 1, // [1:1] is the sub-list for method input_type 249 | 1, // [1:1] is the sub-list for extension type_name 250 | 1, // [1:1] is the sub-list for extension extendee 251 | 0, // [0:1] is the sub-list for field type_name 252 | } 253 | 254 | func init() { file_processor_message_proto_init() } 255 | func file_processor_message_proto_init() { 256 | if File_processor_message_proto != nil { 257 | return 258 | } 259 | file_memory_message_proto_init() 260 | if !protoimpl.UnsafeEnabled { 261 | file_processor_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 262 | switch v := v.(*CPU); i { 263 | case 0: 264 | return &v.state 265 | case 1: 266 | return &v.sizeCache 267 | case 2: 268 | return &v.unknownFields 269 | default: 270 | return nil 271 | } 272 | } 273 | file_processor_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 274 | switch v := v.(*GPU); i { 275 | case 0: 276 | return &v.state 277 | case 1: 278 | return &v.sizeCache 279 | case 2: 280 | return &v.unknownFields 281 | default: 282 | return nil 283 | } 284 | } 285 | } 286 | type x struct{} 287 | out := protoimpl.TypeBuilder{ 288 | File: protoimpl.DescBuilder{ 289 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 290 | RawDescriptor: file_processor_message_proto_rawDesc, 291 | NumEnums: 0, 292 | NumMessages: 2, 293 | NumExtensions: 0, 294 | NumServices: 0, 295 | }, 296 | GoTypes: file_processor_message_proto_goTypes, 297 | DependencyIndexes: file_processor_message_proto_depIdxs, 298 | MessageInfos: file_processor_message_proto_msgTypes, 299 | }.Build() 300 | File_processor_message_proto = out.File 301 | file_processor_message_proto_rawDesc = nil 302 | file_processor_message_proto_goTypes = nil 303 | file_processor_message_proto_depIdxs = nil 304 | } 305 | -------------------------------------------------------------------------------- /pb/screen_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: screen_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type Screen_Panel int32 29 | 30 | const ( 31 | Screen_UNKNOWN Screen_Panel = 0 32 | Screen_IPS Screen_Panel = 1 33 | Screen_OLED Screen_Panel = 2 34 | ) 35 | 36 | // Enum value maps for Screen_Panel. 37 | var ( 38 | Screen_Panel_name = map[int32]string{ 39 | 0: "UNKNOWN", 40 | 1: "IPS", 41 | 2: "OLED", 42 | } 43 | Screen_Panel_value = map[string]int32{ 44 | "UNKNOWN": 0, 45 | "IPS": 1, 46 | "OLED": 2, 47 | } 48 | ) 49 | 50 | func (x Screen_Panel) Enum() *Screen_Panel { 51 | p := new(Screen_Panel) 52 | *p = x 53 | return p 54 | } 55 | 56 | func (x Screen_Panel) String() string { 57 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 58 | } 59 | 60 | func (Screen_Panel) Descriptor() protoreflect.EnumDescriptor { 61 | return file_screen_message_proto_enumTypes[0].Descriptor() 62 | } 63 | 64 | func (Screen_Panel) Type() protoreflect.EnumType { 65 | return &file_screen_message_proto_enumTypes[0] 66 | } 67 | 68 | func (x Screen_Panel) Number() protoreflect.EnumNumber { 69 | return protoreflect.EnumNumber(x) 70 | } 71 | 72 | // Deprecated: Use Screen_Panel.Descriptor instead. 73 | func (Screen_Panel) EnumDescriptor() ([]byte, []int) { 74 | return file_screen_message_proto_rawDescGZIP(), []int{0, 0} 75 | } 76 | 77 | type Screen struct { 78 | state protoimpl.MessageState 79 | sizeCache protoimpl.SizeCache 80 | unknownFields protoimpl.UnknownFields 81 | 82 | SizeInch float32 `protobuf:"fixed32,1,opt,name=size_inch,json=sizeInch,proto3" json:"size_inch,omitempty"` 83 | Resolution *Screen_Resolution `protobuf:"bytes,2,opt,name=resolution,proto3" json:"resolution,omitempty"` 84 | Panel Screen_Panel `protobuf:"varint,3,opt,name=panel,proto3,enum=techschool.pcbook.Screen_Panel" json:"panel,omitempty"` 85 | Multitouch bool `protobuf:"varint,4,opt,name=multitouch,proto3" json:"multitouch,omitempty"` 86 | } 87 | 88 | func (x *Screen) Reset() { 89 | *x = Screen{} 90 | if protoimpl.UnsafeEnabled { 91 | mi := &file_screen_message_proto_msgTypes[0] 92 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 93 | ms.StoreMessageInfo(mi) 94 | } 95 | } 96 | 97 | func (x *Screen) String() string { 98 | return protoimpl.X.MessageStringOf(x) 99 | } 100 | 101 | func (*Screen) ProtoMessage() {} 102 | 103 | func (x *Screen) ProtoReflect() protoreflect.Message { 104 | mi := &file_screen_message_proto_msgTypes[0] 105 | if protoimpl.UnsafeEnabled && x != nil { 106 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 107 | if ms.LoadMessageInfo() == nil { 108 | ms.StoreMessageInfo(mi) 109 | } 110 | return ms 111 | } 112 | return mi.MessageOf(x) 113 | } 114 | 115 | // Deprecated: Use Screen.ProtoReflect.Descriptor instead. 116 | func (*Screen) Descriptor() ([]byte, []int) { 117 | return file_screen_message_proto_rawDescGZIP(), []int{0} 118 | } 119 | 120 | func (x *Screen) GetSizeInch() float32 { 121 | if x != nil { 122 | return x.SizeInch 123 | } 124 | return 0 125 | } 126 | 127 | func (x *Screen) GetResolution() *Screen_Resolution { 128 | if x != nil { 129 | return x.Resolution 130 | } 131 | return nil 132 | } 133 | 134 | func (x *Screen) GetPanel() Screen_Panel { 135 | if x != nil { 136 | return x.Panel 137 | } 138 | return Screen_UNKNOWN 139 | } 140 | 141 | func (x *Screen) GetMultitouch() bool { 142 | if x != nil { 143 | return x.Multitouch 144 | } 145 | return false 146 | } 147 | 148 | type Screen_Resolution struct { 149 | state protoimpl.MessageState 150 | sizeCache protoimpl.SizeCache 151 | unknownFields protoimpl.UnknownFields 152 | 153 | Width uint32 `protobuf:"varint,1,opt,name=width,proto3" json:"width,omitempty"` 154 | Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` 155 | } 156 | 157 | func (x *Screen_Resolution) Reset() { 158 | *x = Screen_Resolution{} 159 | if protoimpl.UnsafeEnabled { 160 | mi := &file_screen_message_proto_msgTypes[1] 161 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 162 | ms.StoreMessageInfo(mi) 163 | } 164 | } 165 | 166 | func (x *Screen_Resolution) String() string { 167 | return protoimpl.X.MessageStringOf(x) 168 | } 169 | 170 | func (*Screen_Resolution) ProtoMessage() {} 171 | 172 | func (x *Screen_Resolution) ProtoReflect() protoreflect.Message { 173 | mi := &file_screen_message_proto_msgTypes[1] 174 | if protoimpl.UnsafeEnabled && x != nil { 175 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 176 | if ms.LoadMessageInfo() == nil { 177 | ms.StoreMessageInfo(mi) 178 | } 179 | return ms 180 | } 181 | return mi.MessageOf(x) 182 | } 183 | 184 | // Deprecated: Use Screen_Resolution.ProtoReflect.Descriptor instead. 185 | func (*Screen_Resolution) Descriptor() ([]byte, []int) { 186 | return file_screen_message_proto_rawDescGZIP(), []int{0, 0} 187 | } 188 | 189 | func (x *Screen_Resolution) GetWidth() uint32 { 190 | if x != nil { 191 | return x.Width 192 | } 193 | return 0 194 | } 195 | 196 | func (x *Screen_Resolution) GetHeight() uint32 { 197 | if x != nil { 198 | return x.Height 199 | } 200 | return 0 201 | } 202 | 203 | var File_screen_message_proto protoreflect.FileDescriptor 204 | 205 | var file_screen_message_proto_rawDesc = []byte{ 206 | 0x0a, 0x14, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 207 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 208 | 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0xa7, 0x02, 0x0a, 0x06, 0x53, 0x63, 209 | 0x72, 0x65, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x69, 0x6e, 0x63, 210 | 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x73, 0x69, 0x7a, 0x65, 0x49, 0x6e, 0x63, 211 | 0x68, 0x12, 0x44, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 212 | 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 213 | 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 214 | 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x72, 0x65, 0x73, 215 | 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x05, 0x70, 0x61, 0x6e, 0x65, 0x6c, 216 | 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 217 | 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 218 | 0x6e, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x52, 0x05, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 219 | 0x0a, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 220 | 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x1a, 0x3a, 221 | 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 222 | 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x77, 0x69, 0x64, 223 | 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 224 | 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x27, 0x0a, 0x05, 0x50, 0x61, 225 | 0x6e, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 226 | 0x12, 0x07, 0x0a, 0x03, 0x49, 0x50, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x4c, 0x45, 227 | 0x44, 0x10, 0x02, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 228 | 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 229 | 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 230 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 231 | } 232 | 233 | var ( 234 | file_screen_message_proto_rawDescOnce sync.Once 235 | file_screen_message_proto_rawDescData = file_screen_message_proto_rawDesc 236 | ) 237 | 238 | func file_screen_message_proto_rawDescGZIP() []byte { 239 | file_screen_message_proto_rawDescOnce.Do(func() { 240 | file_screen_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_screen_message_proto_rawDescData) 241 | }) 242 | return file_screen_message_proto_rawDescData 243 | } 244 | 245 | var file_screen_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 246 | var file_screen_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 247 | var file_screen_message_proto_goTypes = []interface{}{ 248 | (Screen_Panel)(0), // 0: techschool.pcbook.Screen.Panel 249 | (*Screen)(nil), // 1: techschool.pcbook.Screen 250 | (*Screen_Resolution)(nil), // 2: techschool.pcbook.Screen.Resolution 251 | } 252 | var file_screen_message_proto_depIdxs = []int32{ 253 | 2, // 0: techschool.pcbook.Screen.resolution:type_name -> techschool.pcbook.Screen.Resolution 254 | 0, // 1: techschool.pcbook.Screen.panel:type_name -> techschool.pcbook.Screen.Panel 255 | 2, // [2:2] is the sub-list for method output_type 256 | 2, // [2:2] is the sub-list for method input_type 257 | 2, // [2:2] is the sub-list for extension type_name 258 | 2, // [2:2] is the sub-list for extension extendee 259 | 0, // [0:2] is the sub-list for field type_name 260 | } 261 | 262 | func init() { file_screen_message_proto_init() } 263 | func file_screen_message_proto_init() { 264 | if File_screen_message_proto != nil { 265 | return 266 | } 267 | if !protoimpl.UnsafeEnabled { 268 | file_screen_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 269 | switch v := v.(*Screen); i { 270 | case 0: 271 | return &v.state 272 | case 1: 273 | return &v.sizeCache 274 | case 2: 275 | return &v.unknownFields 276 | default: 277 | return nil 278 | } 279 | } 280 | file_screen_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 281 | switch v := v.(*Screen_Resolution); i { 282 | case 0: 283 | return &v.state 284 | case 1: 285 | return &v.sizeCache 286 | case 2: 287 | return &v.unknownFields 288 | default: 289 | return nil 290 | } 291 | } 292 | } 293 | type x struct{} 294 | out := protoimpl.TypeBuilder{ 295 | File: protoimpl.DescBuilder{ 296 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 297 | RawDescriptor: file_screen_message_proto_rawDesc, 298 | NumEnums: 1, 299 | NumMessages: 2, 300 | NumExtensions: 0, 301 | NumServices: 0, 302 | }, 303 | GoTypes: file_screen_message_proto_goTypes, 304 | DependencyIndexes: file_screen_message_proto_depIdxs, 305 | EnumInfos: file_screen_message_proto_enumTypes, 306 | MessageInfos: file_screen_message_proto_msgTypes, 307 | }.Build() 308 | File_screen_message_proto = out.File 309 | file_screen_message_proto_rawDesc = nil 310 | file_screen_message_proto_goTypes = nil 311 | file_screen_message_proto_depIdxs = nil 312 | } 313 | -------------------------------------------------------------------------------- /pb/storage_message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.14.0 5 | // source: storage_message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type Storage_Driver int32 29 | 30 | const ( 31 | Storage_UNKNOWN Storage_Driver = 0 32 | Storage_HDD Storage_Driver = 1 33 | Storage_SSD Storage_Driver = 2 34 | ) 35 | 36 | // Enum value maps for Storage_Driver. 37 | var ( 38 | Storage_Driver_name = map[int32]string{ 39 | 0: "UNKNOWN", 40 | 1: "HDD", 41 | 2: "SSD", 42 | } 43 | Storage_Driver_value = map[string]int32{ 44 | "UNKNOWN": 0, 45 | "HDD": 1, 46 | "SSD": 2, 47 | } 48 | ) 49 | 50 | func (x Storage_Driver) Enum() *Storage_Driver { 51 | p := new(Storage_Driver) 52 | *p = x 53 | return p 54 | } 55 | 56 | func (x Storage_Driver) String() string { 57 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 58 | } 59 | 60 | func (Storage_Driver) Descriptor() protoreflect.EnumDescriptor { 61 | return file_storage_message_proto_enumTypes[0].Descriptor() 62 | } 63 | 64 | func (Storage_Driver) Type() protoreflect.EnumType { 65 | return &file_storage_message_proto_enumTypes[0] 66 | } 67 | 68 | func (x Storage_Driver) Number() protoreflect.EnumNumber { 69 | return protoreflect.EnumNumber(x) 70 | } 71 | 72 | // Deprecated: Use Storage_Driver.Descriptor instead. 73 | func (Storage_Driver) EnumDescriptor() ([]byte, []int) { 74 | return file_storage_message_proto_rawDescGZIP(), []int{0, 0} 75 | } 76 | 77 | type Storage struct { 78 | state protoimpl.MessageState 79 | sizeCache protoimpl.SizeCache 80 | unknownFields protoimpl.UnknownFields 81 | 82 | Driver Storage_Driver `protobuf:"varint,1,opt,name=driver,proto3,enum=techschool.pcbook.Storage_Driver" json:"driver,omitempty"` 83 | Memory *Memory `protobuf:"bytes,2,opt,name=memory,proto3" json:"memory,omitempty"` 84 | } 85 | 86 | func (x *Storage) Reset() { 87 | *x = Storage{} 88 | if protoimpl.UnsafeEnabled { 89 | mi := &file_storage_message_proto_msgTypes[0] 90 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 91 | ms.StoreMessageInfo(mi) 92 | } 93 | } 94 | 95 | func (x *Storage) String() string { 96 | return protoimpl.X.MessageStringOf(x) 97 | } 98 | 99 | func (*Storage) ProtoMessage() {} 100 | 101 | func (x *Storage) ProtoReflect() protoreflect.Message { 102 | mi := &file_storage_message_proto_msgTypes[0] 103 | if protoimpl.UnsafeEnabled && x != nil { 104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 105 | if ms.LoadMessageInfo() == nil { 106 | ms.StoreMessageInfo(mi) 107 | } 108 | return ms 109 | } 110 | return mi.MessageOf(x) 111 | } 112 | 113 | // Deprecated: Use Storage.ProtoReflect.Descriptor instead. 114 | func (*Storage) Descriptor() ([]byte, []int) { 115 | return file_storage_message_proto_rawDescGZIP(), []int{0} 116 | } 117 | 118 | func (x *Storage) GetDriver() Storage_Driver { 119 | if x != nil { 120 | return x.Driver 121 | } 122 | return Storage_UNKNOWN 123 | } 124 | 125 | func (x *Storage) GetMemory() *Memory { 126 | if x != nil { 127 | return x.Memory 128 | } 129 | return nil 130 | } 131 | 132 | var File_storage_message_proto protoreflect.FileDescriptor 133 | 134 | var file_storage_message_proto_rawDesc = []byte{ 135 | 0x0a, 0x15, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 136 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 137 | 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 138 | 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 139 | 0x22, 0xa0, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x06, 140 | 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x74, 141 | 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 142 | 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x72, 0x69, 0x76, 0x65, 0x72, 0x52, 143 | 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 144 | 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 145 | 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 146 | 0x72, 0x79, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x27, 0x0a, 0x06, 0x44, 0x72, 147 | 0x69, 0x76, 0x65, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 148 | 0x00, 0x12, 0x07, 0x0a, 0x03, 0x48, 0x44, 0x44, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 149 | 0x44, 0x10, 0x02, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 150 | 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 151 | 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 152 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 153 | } 154 | 155 | var ( 156 | file_storage_message_proto_rawDescOnce sync.Once 157 | file_storage_message_proto_rawDescData = file_storage_message_proto_rawDesc 158 | ) 159 | 160 | func file_storage_message_proto_rawDescGZIP() []byte { 161 | file_storage_message_proto_rawDescOnce.Do(func() { 162 | file_storage_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_storage_message_proto_rawDescData) 163 | }) 164 | return file_storage_message_proto_rawDescData 165 | } 166 | 167 | var file_storage_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 168 | var file_storage_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 169 | var file_storage_message_proto_goTypes = []interface{}{ 170 | (Storage_Driver)(0), // 0: techschool.pcbook.Storage.Driver 171 | (*Storage)(nil), // 1: techschool.pcbook.Storage 172 | (*Memory)(nil), // 2: techschool.pcbook.Memory 173 | } 174 | var file_storage_message_proto_depIdxs = []int32{ 175 | 0, // 0: techschool.pcbook.Storage.driver:type_name -> techschool.pcbook.Storage.Driver 176 | 2, // 1: techschool.pcbook.Storage.memory:type_name -> techschool.pcbook.Memory 177 | 2, // [2:2] is the sub-list for method output_type 178 | 2, // [2:2] is the sub-list for method input_type 179 | 2, // [2:2] is the sub-list for extension type_name 180 | 2, // [2:2] is the sub-list for extension extendee 181 | 0, // [0:2] is the sub-list for field type_name 182 | } 183 | 184 | func init() { file_storage_message_proto_init() } 185 | func file_storage_message_proto_init() { 186 | if File_storage_message_proto != nil { 187 | return 188 | } 189 | file_memory_message_proto_init() 190 | if !protoimpl.UnsafeEnabled { 191 | file_storage_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 192 | switch v := v.(*Storage); i { 193 | case 0: 194 | return &v.state 195 | case 1: 196 | return &v.sizeCache 197 | case 2: 198 | return &v.unknownFields 199 | default: 200 | return nil 201 | } 202 | } 203 | } 204 | type x struct{} 205 | out := protoimpl.TypeBuilder{ 206 | File: protoimpl.DescBuilder{ 207 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 208 | RawDescriptor: file_storage_message_proto_rawDesc, 209 | NumEnums: 1, 210 | NumMessages: 1, 211 | NumExtensions: 0, 212 | NumServices: 0, 213 | }, 214 | GoTypes: file_storage_message_proto_goTypes, 215 | DependencyIndexes: file_storage_message_proto_depIdxs, 216 | EnumInfos: file_storage_message_proto_enumTypes, 217 | MessageInfos: file_storage_message_proto_msgTypes, 218 | }.Build() 219 | File_storage_message_proto = out.File 220 | file_storage_message_proto_rawDesc = nil 221 | file_storage_message_proto_goTypes = nil 222 | file_storage_message_proto_depIdxs = nil 223 | } 224 | -------------------------------------------------------------------------------- /proto/auth_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | import "google/api/annotations.proto"; 6 | 7 | option go_package = ".;pb"; 8 | option java_package = "com.gitlab.techschool.pcbook.pb"; 9 | option java_multiple_files = true; 10 | 11 | message LoginRequest { 12 | string username = 1; 13 | string password = 2; 14 | } 15 | 16 | message LoginResponse { string access_token = 1; } 17 | 18 | service AuthService { 19 | rpc Login(LoginRequest) returns (LoginResponse) { 20 | option (google.api.http) = { 21 | post : "/v1/auth/login" 22 | body : "*" 23 | }; 24 | }; 25 | } -------------------------------------------------------------------------------- /proto/filter_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | import "memory_message.proto"; 10 | 11 | message Filter { 12 | double max_price_usd = 1; 13 | uint32 min_cpu_cores = 2; 14 | double min_cpu_ghz = 3; 15 | Memory min_ram = 4; 16 | } 17 | -------------------------------------------------------------------------------- /proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /proto/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | syntax = "proto3"; 17 | 18 | package google.api; 19 | 20 | import "google/protobuf/any.proto"; 21 | 22 | option cc_enable_arenas = true; 23 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HttpBodyProto"; 26 | option java_package = "com.google.api"; 27 | option objc_class_prefix = "GAPI"; 28 | 29 | // Message that represents an arbitrary HTTP body. It should only be used for 30 | // payload formats that can't be represented as JSON, such as raw binary or 31 | // an HTML page. 32 | // 33 | // 34 | // This message can be used both in streaming and non-streaming API methods in 35 | // the request as well as the response. 36 | // 37 | // It can be used as a top-level request field, which is convenient if one 38 | // wants to extract parameters from either the URL or HTTP template into the 39 | // request fields and also want access to the raw HTTP body. 40 | // 41 | // Example: 42 | // 43 | // message GetResourceRequest { 44 | // // A unique request id. 45 | // string request_id = 1; 46 | // 47 | // // The raw HTTP body is bound to this field. 48 | // google.api.HttpBody http_body = 2; 49 | // } 50 | // 51 | // service ResourceService { 52 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 53 | // rpc UpdateResource(google.api.HttpBody) returns 54 | // (google.protobuf.Empty); 55 | // } 56 | // 57 | // Example with streaming methods: 58 | // 59 | // service CaldavService { 60 | // rpc GetCalendar(stream google.api.HttpBody) 61 | // returns (stream google.api.HttpBody); 62 | // rpc UpdateCalendar(stream google.api.HttpBody) 63 | // returns (stream google.api.HttpBody); 64 | // } 65 | // 66 | // Use of this type only changes how the request and response bodies are 67 | // handled, all other features will continue to work unchanged. 68 | message HttpBody { 69 | // The HTTP Content-Type header value specifying the content type of the body. 70 | string content_type = 1; 71 | 72 | // The HTTP request/response body as raw binary. 73 | bytes data = 2; 74 | 75 | // Application specific response metadata. Must be set in the first response 76 | // for streaming APIs. 77 | repeated google.protobuf.Any extensions = 3; 78 | } -------------------------------------------------------------------------------- /proto/google/rpc/code.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; 20 | option java_multiple_files = true; 21 | option java_outer_classname = "CodeProto"; 22 | option java_package = "com.google.rpc"; 23 | option objc_class_prefix = "RPC"; 24 | 25 | 26 | // The canonical error codes for Google APIs. 27 | // 28 | // 29 | // Sometimes multiple error codes may apply. Services should return 30 | // the most specific error code that applies. For example, prefer 31 | // `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. 32 | // Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. 33 | enum Code { 34 | // Not an error; returned on success 35 | // 36 | // HTTP Mapping: 200 OK 37 | OK = 0; 38 | 39 | // The operation was cancelled, typically by the caller. 40 | // 41 | // HTTP Mapping: 499 Client Closed Request 42 | CANCELLED = 1; 43 | 44 | // Unknown error. For example, this error may be returned when 45 | // a `Status` value received from another address space belongs to 46 | // an error space that is not known in this address space. Also 47 | // errors raised by APIs that do not return enough error information 48 | // may be converted to this error. 49 | // 50 | // HTTP Mapping: 500 Internal Server Error 51 | UNKNOWN = 2; 52 | 53 | // The client specified an invalid argument. Note that this differs 54 | // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments 55 | // that are problematic regardless of the state of the system 56 | // (e.g., a malformed file name). 57 | // 58 | // HTTP Mapping: 400 Bad Request 59 | INVALID_ARGUMENT = 3; 60 | 61 | // The deadline expired before the operation could complete. For operations 62 | // that change the state of the system, this error may be returned 63 | // even if the operation has completed successfully. For example, a 64 | // successful response from a server could have been delayed long 65 | // enough for the deadline to expire. 66 | // 67 | // HTTP Mapping: 504 Gateway Timeout 68 | DEADLINE_EXCEEDED = 4; 69 | 70 | // Some requested entity (e.g., file or directory) was not found. 71 | // 72 | // Note to server developers: if a request is denied for an entire class 73 | // of users, such as gradual feature rollout or undocumented whitelist, 74 | // `NOT_FOUND` may be used. If a request is denied for some users within 75 | // a class of users, such as user-based access control, `PERMISSION_DENIED` 76 | // must be used. 77 | // 78 | // HTTP Mapping: 404 Not Found 79 | NOT_FOUND = 5; 80 | 81 | // The entity that a client attempted to create (e.g., file or directory) 82 | // already exists. 83 | // 84 | // HTTP Mapping: 409 Conflict 85 | ALREADY_EXISTS = 6; 86 | 87 | // The caller does not have permission to execute the specified 88 | // operation. `PERMISSION_DENIED` must not be used for rejections 89 | // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` 90 | // instead for those errors). `PERMISSION_DENIED` must not be 91 | // used if the caller can not be identified (use `UNAUTHENTICATED` 92 | // instead for those errors). This error code does not imply the 93 | // request is valid or the requested entity exists or satisfies 94 | // other pre-conditions. 95 | // 96 | // HTTP Mapping: 403 Forbidden 97 | PERMISSION_DENIED = 7; 98 | 99 | // The request does not have valid authentication credentials for the 100 | // operation. 101 | // 102 | // HTTP Mapping: 401 Unauthorized 103 | UNAUTHENTICATED = 16; 104 | 105 | // Some resource has been exhausted, perhaps a per-user quota, or 106 | // perhaps the entire file system is out of space. 107 | // 108 | // HTTP Mapping: 429 Too Many Requests 109 | RESOURCE_EXHAUSTED = 8; 110 | 111 | // The operation was rejected because the system is not in a state 112 | // required for the operation's execution. For example, the directory 113 | // to be deleted is non-empty, an rmdir operation is applied to 114 | // a non-directory, etc. 115 | // 116 | // Service implementors can use the following guidelines to decide 117 | // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: 118 | // (a) Use `UNAVAILABLE` if the client can retry just the failing call. 119 | // (b) Use `ABORTED` if the client should retry at a higher level 120 | // (e.g., when a client-specified test-and-set fails, indicating the 121 | // client should restart a read-modify-write sequence). 122 | // (c) Use `FAILED_PRECONDITION` if the client should not retry until 123 | // the system state has been explicitly fixed. E.g., if an "rmdir" 124 | // fails because the directory is non-empty, `FAILED_PRECONDITION` 125 | // should be returned since the client should not retry unless 126 | // the files are deleted from the directory. 127 | // 128 | // HTTP Mapping: 400 Bad Request 129 | FAILED_PRECONDITION = 9; 130 | 131 | // The operation was aborted, typically due to a concurrency issue such as 132 | // a sequencer check failure or transaction abort. 133 | // 134 | // See the guidelines above for deciding between `FAILED_PRECONDITION`, 135 | // `ABORTED`, and `UNAVAILABLE`. 136 | // 137 | // HTTP Mapping: 409 Conflict 138 | ABORTED = 10; 139 | 140 | // The operation was attempted past the valid range. E.g., seeking or 141 | // reading past end-of-file. 142 | // 143 | // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may 144 | // be fixed if the system state changes. For example, a 32-bit file 145 | // system will generate `INVALID_ARGUMENT` if asked to read at an 146 | // offset that is not in the range [0,2^32-1], but it will generate 147 | // `OUT_OF_RANGE` if asked to read from an offset past the current 148 | // file size. 149 | // 150 | // There is a fair bit of overlap between `FAILED_PRECONDITION` and 151 | // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific 152 | // error) when it applies so that callers who are iterating through 153 | // a space can easily look for an `OUT_OF_RANGE` error to detect when 154 | // they are done. 155 | // 156 | // HTTP Mapping: 400 Bad Request 157 | OUT_OF_RANGE = 11; 158 | 159 | // The operation is not implemented or is not supported/enabled in this 160 | // service. 161 | // 162 | // HTTP Mapping: 501 Not Implemented 163 | UNIMPLEMENTED = 12; 164 | 165 | // Internal errors. This means that some invariants expected by the 166 | // underlying system have been broken. This error code is reserved 167 | // for serious errors. 168 | // 169 | // HTTP Mapping: 500 Internal Server Error 170 | INTERNAL = 13; 171 | 172 | // The service is currently unavailable. This is most likely a 173 | // transient condition, which can be corrected by retrying with 174 | // a backoff. 175 | // 176 | // See the guidelines above for deciding between `FAILED_PRECONDITION`, 177 | // `ABORTED`, and `UNAVAILABLE`. 178 | // 179 | // HTTP Mapping: 503 Service Unavailable 180 | UNAVAILABLE = 14; 181 | 182 | // Unrecoverable data loss or corruption. 183 | // 184 | // HTTP Mapping: 500 Internal Server Error 185 | DATA_LOSS = 15; 186 | } 187 | -------------------------------------------------------------------------------- /proto/google/rpc/error_details.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | import "google/protobuf/duration.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "ErrorDetailsProto"; 24 | option java_package = "com.google.rpc"; 25 | option objc_class_prefix = "RPC"; 26 | 27 | 28 | // Describes when the clients can retry a failed request. Clients could ignore 29 | // the recommendation here or retry when this information is missing from error 30 | // responses. 31 | // 32 | // It's always recommended that clients should use exponential backoff when 33 | // retrying. 34 | // 35 | // Clients should wait until `retry_delay` amount of time has passed since 36 | // receiving the error response before retrying. If retrying requests also 37 | // fail, clients should use an exponential backoff scheme to gradually increase 38 | // the delay between retries based on `retry_delay`, until either a maximum 39 | // number of retires have been reached or a maximum retry delay cap has been 40 | // reached. 41 | message RetryInfo { 42 | // Clients should wait at least this long between retrying the same request. 43 | google.protobuf.Duration retry_delay = 1; 44 | } 45 | 46 | // Describes additional debugging info. 47 | message DebugInfo { 48 | // The stack trace entries indicating where the error occurred. 49 | repeated string stack_entries = 1; 50 | 51 | // Additional debugging information provided by the server. 52 | string detail = 2; 53 | } 54 | 55 | // Describes how a quota check failed. 56 | // 57 | // For example if a daily limit was exceeded for the calling project, 58 | // a service could respond with a QuotaFailure detail containing the project 59 | // id and the description of the quota limit that was exceeded. If the 60 | // calling project hasn't enabled the service in the developer console, then 61 | // a service could respond with the project id and set `service_disabled` 62 | // to true. 63 | // 64 | // Also see RetryDetail and Help types for other details about handling a 65 | // quota failure. 66 | message QuotaFailure { 67 | // A message type used to describe a single quota violation. For example, a 68 | // daily quota or a custom quota that was exceeded. 69 | message Violation { 70 | // The subject on which the quota check failed. 71 | // For example, "clientip:" or "project:". 73 | string subject = 1; 74 | 75 | // A description of how the quota check failed. Clients can use this 76 | // description to find more about the quota configuration in the service's 77 | // public documentation, or find the relevant quota limit to adjust through 78 | // developer console. 79 | // 80 | // For example: "Service disabled" or "Daily Limit for read operations 81 | // exceeded". 82 | string description = 2; 83 | } 84 | 85 | // Describes all quota violations. 86 | repeated Violation violations = 1; 87 | } 88 | 89 | // Describes what preconditions have failed. 90 | // 91 | // For example, if an RPC failed because it required the Terms of Service to be 92 | // acknowledged, it could list the terms of service violation in the 93 | // PreconditionFailure message. 94 | message PreconditionFailure { 95 | // A message type used to describe a single precondition failure. 96 | message Violation { 97 | // The type of PreconditionFailure. We recommend using a service-specific 98 | // enum type to define the supported precondition violation types. For 99 | // example, "TOS" for "Terms of Service violation". 100 | string type = 1; 101 | 102 | // The subject, relative to the type, that failed. 103 | // For example, "google.com/cloud" relative to the "TOS" type would 104 | // indicate which terms of service is being referenced. 105 | string subject = 2; 106 | 107 | // A description of how the precondition failed. Developers can use this 108 | // description to understand how to fix the failure. 109 | // 110 | // For example: "Terms of service not accepted". 111 | string description = 3; 112 | } 113 | 114 | // Describes all precondition violations. 115 | repeated Violation violations = 1; 116 | } 117 | 118 | // Describes violations in a client request. This error type focuses on the 119 | // syntactic aspects of the request. 120 | message BadRequest { 121 | // A message type used to describe a single bad request field. 122 | message FieldViolation { 123 | // A path leading to a field in the request body. The value will be a 124 | // sequence of dot-separated identifiers that identify a protocol buffer 125 | // field. E.g., "field_violations.field" would identify this field. 126 | string field = 1; 127 | 128 | // A description of why the request element is bad. 129 | string description = 2; 130 | } 131 | 132 | // Describes all violations in a client request. 133 | repeated FieldViolation field_violations = 1; 134 | } 135 | 136 | // Contains metadata about the request that clients can attach when filing a bug 137 | // or providing other forms of feedback. 138 | message RequestInfo { 139 | // An opaque string that should only be interpreted by the service generating 140 | // it. For example, it can be used to identify requests in the service's logs. 141 | string request_id = 1; 142 | 143 | // Any data that was used to serve this request. For example, an encrypted 144 | // stack trace that can be sent back to the service provider for debugging. 145 | string serving_data = 2; 146 | } 147 | 148 | // Describes the resource that is being accessed. 149 | message ResourceInfo { 150 | // A name for the type of resource being accessed, e.g. "sql table", 151 | // "cloud storage bucket", "file", "Google calendar"; or the type URL 152 | // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". 153 | string resource_type = 1; 154 | 155 | // The name of the resource being accessed. For example, a shared calendar 156 | // name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current 157 | // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED]. 158 | string resource_name = 2; 159 | 160 | // The owner of the resource (optional). 161 | // For example, "user:" or "project:". 163 | string owner = 3; 164 | 165 | // Describes what error is encountered when accessing this resource. 166 | // For example, updating a cloud project may require the `writer` permission 167 | // on the developer console project. 168 | string description = 4; 169 | } 170 | 171 | // Provides links to documentation or for performing an out of band action. 172 | // 173 | // For example, if a quota check failed with an error indicating the calling 174 | // project hasn't enabled the accessed service, this can contain a URL pointing 175 | // directly to the right place in the developer console to flip the bit. 176 | message Help { 177 | // Describes a URL link. 178 | message Link { 179 | // Describes what the link offers. 180 | string description = 1; 181 | 182 | // The URL of the link. 183 | string url = 2; 184 | } 185 | 186 | // URL(s) pointing to additional information on handling the current error. 187 | repeated Link links = 1; 188 | } 189 | 190 | // Provides a localized error message that is safe to return to the user 191 | // which can be attached to an RPC error. 192 | message LocalizedMessage { 193 | // The locale used following the specification defined at 194 | // http://www.rfc-editor.org/rfc/bcp/bcp47.txt. 195 | // Examples are: "en-US", "fr-CH", "es-MX" 196 | string locale = 1; 197 | 198 | // The localized error message in the above locale. 199 | string message = 2; 200 | } 201 | -------------------------------------------------------------------------------- /proto/google/rpc/status.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "StatusProto"; 24 | option java_package = "com.google.rpc"; 25 | option objc_class_prefix = "RPC"; 26 | 27 | 28 | // The `Status` type defines a logical error model that is suitable for different 29 | // programming environments, including REST APIs and RPC APIs. It is used by 30 | // [gRPC](https://github.com/grpc). The error model is designed to be: 31 | // 32 | // - Simple to use and understand for most users 33 | // - Flexible enough to meet unexpected needs 34 | // 35 | // # Overview 36 | // 37 | // The `Status` message contains three pieces of data: error code, error message, 38 | // and error details. The error code should be an enum value of 39 | // [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The 40 | // error message should be a developer-facing English message that helps 41 | // developers *understand* and *resolve* the error. If a localized user-facing 42 | // error message is needed, put the localized message in the error details or 43 | // localize it in the client. The optional error details may contain arbitrary 44 | // information about the error. There is a predefined set of error detail types 45 | // in the package `google.rpc` that can be used for common error conditions. 46 | // 47 | // # Language mapping 48 | // 49 | // The `Status` message is the logical representation of the error model, but it 50 | // is not necessarily the actual wire format. When the `Status` message is 51 | // exposed in different client libraries and different wire protocols, it can be 52 | // mapped differently. For example, it will likely be mapped to some exceptions 53 | // in Java, but more likely mapped to some error codes in C. 54 | // 55 | // # Other uses 56 | // 57 | // The error model and the `Status` message can be used in a variety of 58 | // environments, either with or without APIs, to provide a 59 | // consistent developer experience across different environments. 60 | // 61 | // Example uses of this error model include: 62 | // 63 | // - Partial errors. If a service needs to return partial errors to the client, 64 | // it may embed the `Status` in the normal response to indicate the partial 65 | // errors. 66 | // 67 | // - Workflow errors. A typical workflow has multiple steps. Each step may 68 | // have a `Status` message for error reporting. 69 | // 70 | // - Batch operations. If a client uses batch request and batch response, the 71 | // `Status` message should be used directly inside batch response, one for 72 | // each error sub-response. 73 | // 74 | // - Asynchronous operations. If an API call embeds asynchronous operation 75 | // results in its response, the status of those operations should be 76 | // represented directly using the `Status` message. 77 | // 78 | // - Logging. If some API errors are stored in logs, the message `Status` could 79 | // be used directly after any stripping needed for security/privacy reasons. 80 | message Status { 81 | // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. 82 | int32 code = 1; 83 | 84 | // A developer-facing error message, which should be in English. Any 85 | // user-facing error message should be localized and sent in the 86 | // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. 87 | string message = 2; 88 | 89 | // A list of messages that carry the error details. There is a common set of 90 | // message types for APIs to use. 91 | repeated google.protobuf.Any details = 3; 92 | } 93 | -------------------------------------------------------------------------------- /proto/keyboard_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | message Keyboard { 10 | enum Layout { 11 | UNKNOWN = 0; 12 | QWERTY = 1; 13 | QWERTZ = 2; 14 | AZERTY = 3; 15 | } 16 | 17 | Layout layout = 1; 18 | bool backlit = 2; 19 | } 20 | -------------------------------------------------------------------------------- /proto/laptop_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | import "processor_message.proto"; 10 | import "memory_message.proto"; 11 | import "storage_message.proto"; 12 | import "screen_message.proto"; 13 | import "keyboard_message.proto"; 14 | import "google/protobuf/timestamp.proto"; 15 | 16 | message Laptop { 17 | string id = 1; 18 | string brand = 2; 19 | string name = 3; 20 | CPU cpu = 4; 21 | Memory ram = 5; 22 | repeated GPU gpus = 6; 23 | repeated Storage storages = 7; 24 | Screen screen = 8; 25 | Keyboard keyboard = 9; 26 | oneof weight { 27 | double weight_kg = 10; 28 | double weight_lb = 11; 29 | } 30 | double price_usd = 12; 31 | uint32 release_year = 13; 32 | google.protobuf.Timestamp updated_at = 14; 33 | } 34 | -------------------------------------------------------------------------------- /proto/laptop_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | import "laptop_message.proto"; 10 | import "filter_message.proto"; 11 | import "google/api/annotations.proto"; 12 | 13 | message CreateLaptopRequest { Laptop laptop = 1; } 14 | 15 | message CreateLaptopResponse { string id = 1; } 16 | 17 | message SearchLaptopRequest { Filter filter = 1; } 18 | 19 | message SearchLaptopResponse { Laptop laptop = 1; } 20 | 21 | message UploadImageRequest { 22 | oneof data { 23 | ImageInfo info = 1; 24 | bytes chunk_data = 2; 25 | }; 26 | } 27 | 28 | message ImageInfo { 29 | string laptop_id = 1; 30 | string image_type = 2; 31 | } 32 | 33 | message UploadImageResponse { 34 | string id = 1; 35 | uint32 size = 2; 36 | } 37 | 38 | message RateLaptopRequest { 39 | string laptop_id = 1; 40 | double score = 2; 41 | } 42 | 43 | message RateLaptopResponse { 44 | string laptop_id = 1; 45 | uint32 rated_count = 2; 46 | double average_score = 3; 47 | } 48 | 49 | service LaptopService { 50 | rpc CreateLaptop(CreateLaptopRequest) returns (CreateLaptopResponse) { 51 | option (google.api.http) = { 52 | post : "/v1/laptop/create" 53 | body : "*" 54 | }; 55 | }; 56 | rpc SearchLaptop(SearchLaptopRequest) returns (stream SearchLaptopResponse) { 57 | option (google.api.http) = { 58 | get : "/v1/laptop/search" 59 | }; 60 | }; 61 | rpc UploadImage(stream UploadImageRequest) returns (UploadImageResponse) { 62 | option (google.api.http) = { 63 | post : "/v1/laptop/upload_image" 64 | body : "*" 65 | }; 66 | }; 67 | rpc RateLaptop(stream RateLaptopRequest) returns (stream RateLaptopResponse) { 68 | option (google.api.http) = { 69 | post : "/v1/laptop/rate" 70 | body : "*" 71 | }; 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /proto/memory_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | message Memory { 10 | enum Unit { 11 | UNKNOWN = 0; 12 | BIT = 1; 13 | BYTE = 2; 14 | KILOBYTE = 3; 15 | MEGABYTE = 4; 16 | GIGABYTE = 5; 17 | TERABYTE = 6; 18 | } 19 | 20 | uint64 value = 1; 21 | Unit unit = 2; 22 | } 23 | -------------------------------------------------------------------------------- /proto/processor_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | import "memory_message.proto"; 10 | 11 | message CPU { 12 | string brand = 1; 13 | string name = 2; 14 | uint32 number_cores = 3; 15 | uint32 number_threads = 4; 16 | double min_ghz = 5; 17 | double max_ghz = 6; 18 | } 19 | 20 | message GPU { 21 | string brand = 1; 22 | string name = 2; 23 | double min_ghz = 3; 24 | double max_ghz = 4; 25 | Memory memory = 5; 26 | } 27 | -------------------------------------------------------------------------------- /proto/screen_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | message Screen { 10 | message Resolution { 11 | uint32 width = 1; 12 | uint32 height = 2; 13 | } 14 | 15 | enum Panel { 16 | UNKNOWN = 0; 17 | IPS = 1; 18 | OLED = 2; 19 | } 20 | 21 | float size_inch = 1; 22 | Resolution resolution = 2; 23 | Panel panel = 3; 24 | bool multitouch = 4; 25 | } 26 | -------------------------------------------------------------------------------- /proto/storage_message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package techschool.pcbook; 4 | 5 | option go_package = ".;pb"; 6 | option java_package = "com.gitlab.techschool.pcbook.pb"; 7 | option java_multiple_files = true; 8 | 9 | import "memory_message.proto"; 10 | 11 | message Storage { 12 | enum Driver { 13 | UNKNOWN = 0; 14 | HDD = 1; 15 | SSD = 2; 16 | } 17 | 18 | Driver driver = 1; 19 | Memory memory = 2; 20 | } 21 | -------------------------------------------------------------------------------- /sample/laptop.go: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import ( 4 | "github.com/golang/protobuf/ptypes" 5 | "gitlab.com/techschool/pcbook/pb" 6 | ) 7 | 8 | // NewKeyboard returns a new sample keyboard 9 | func NewKeyboard() *pb.Keyboard { 10 | keyboard := &pb.Keyboard{ 11 | Layout: randomKeyboardLayout(), 12 | Backlit: randomBool(), 13 | } 14 | 15 | return keyboard 16 | } 17 | 18 | // NewCPU returns a new sample CPU 19 | func NewCPU() *pb.CPU { 20 | brand := randomCPUBrand() 21 | name := randomCPUName(brand) 22 | 23 | numberCores := randomInt(2, 8) 24 | numberThreads := randomInt(numberCores, 12) 25 | 26 | minGhz := randomFloat64(2.0, 3.5) 27 | maxGhz := randomFloat64(minGhz, 5.0) 28 | 29 | cpu := &pb.CPU{ 30 | Brand: brand, 31 | Name: name, 32 | NumberCores: uint32(numberCores), 33 | NumberThreads: uint32(numberThreads), 34 | MinGhz: minGhz, 35 | MaxGhz: maxGhz, 36 | } 37 | 38 | return cpu 39 | } 40 | 41 | // NewGPU returns a new sample GPU 42 | func NewGPU() *pb.GPU { 43 | brand := randomGPUBrand() 44 | name := randomGPUName(brand) 45 | 46 | minGhz := randomFloat64(1.0, 1.5) 47 | maxGhz := randomFloat64(minGhz, 2.0) 48 | memGB := randomInt(2, 6) 49 | 50 | gpu := &pb.GPU{ 51 | Brand: brand, 52 | Name: name, 53 | MinGhz: minGhz, 54 | MaxGhz: maxGhz, 55 | Memory: &pb.Memory{ 56 | Value: uint64(memGB), 57 | Unit: pb.Memory_GIGABYTE, 58 | }, 59 | } 60 | 61 | return gpu 62 | } 63 | 64 | // NewRAM returns a new sample RAM 65 | func NewRAM() *pb.Memory { 66 | memGB := randomInt(4, 64) 67 | 68 | ram := &pb.Memory{ 69 | Value: uint64(memGB), 70 | Unit: pb.Memory_GIGABYTE, 71 | } 72 | 73 | return ram 74 | } 75 | 76 | // NewSSD returns a new sample SSD 77 | func NewSSD() *pb.Storage { 78 | memGB := randomInt(128, 1024) 79 | 80 | ssd := &pb.Storage{ 81 | Driver: pb.Storage_SSD, 82 | Memory: &pb.Memory{ 83 | Value: uint64(memGB), 84 | Unit: pb.Memory_GIGABYTE, 85 | }, 86 | } 87 | 88 | return ssd 89 | } 90 | 91 | // NewHDD returns a new sample HDD 92 | func NewHDD() *pb.Storage { 93 | memTB := randomInt(1, 6) 94 | 95 | hdd := &pb.Storage{ 96 | Driver: pb.Storage_HDD, 97 | Memory: &pb.Memory{ 98 | Value: uint64(memTB), 99 | Unit: pb.Memory_TERABYTE, 100 | }, 101 | } 102 | 103 | return hdd 104 | } 105 | 106 | // NewScreen returns a new sample Screen 107 | func NewScreen() *pb.Screen { 108 | screen := &pb.Screen{ 109 | SizeInch: randomFloat32(13, 17), 110 | Resolution: randomScreenResolution(), 111 | Panel: randomScreenPanel(), 112 | Multitouch: randomBool(), 113 | } 114 | 115 | return screen 116 | } 117 | 118 | // NewLaptop returns a new sample Laptop 119 | func NewLaptop() *pb.Laptop { 120 | brand := randomLaptopBrand() 121 | name := randomLaptopName(brand) 122 | 123 | laptop := &pb.Laptop{ 124 | Id: randomID(), 125 | Brand: brand, 126 | Name: name, 127 | Cpu: NewCPU(), 128 | Ram: NewRAM(), 129 | Gpus: []*pb.GPU{NewGPU()}, 130 | Storages: []*pb.Storage{NewSSD(), NewHDD()}, 131 | Screen: NewScreen(), 132 | Keyboard: NewKeyboard(), 133 | Weight: &pb.Laptop_WeightKg{ 134 | WeightKg: randomFloat64(1.0, 3.0), 135 | }, 136 | PriceUsd: randomFloat64(1500, 3500), 137 | ReleaseYear: uint32(randomInt(2015, 2019)), 138 | UpdatedAt: ptypes.TimestampNow(), 139 | } 140 | 141 | return laptop 142 | } 143 | 144 | // RandomLaptopScore returns a random laptop score 145 | func RandomLaptopScore() float64 { 146 | return float64(randomInt(1, 10)) 147 | } 148 | -------------------------------------------------------------------------------- /sample/random.go: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | "gitlab.com/techschool/pcbook/pb" 9 | ) 10 | 11 | func init() { 12 | rand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | func randomStringFromSet(a ...string) string { 16 | n := len(a) 17 | if n == 0 { 18 | return "" 19 | } 20 | return a[rand.Intn(n)] 21 | } 22 | 23 | func randomBool() bool { 24 | return rand.Intn(2) == 1 25 | } 26 | 27 | func randomInt(min, max int) int { 28 | return min + rand.Int()%(max-min+1) 29 | } 30 | 31 | func randomFloat64(min, max float64) float64 { 32 | return min + rand.Float64()*(max-min) 33 | } 34 | 35 | func randomFloat32(min, max float32) float32 { 36 | return min + rand.Float32()*(max-min) 37 | } 38 | 39 | func randomID() string { 40 | return uuid.New().String() 41 | } 42 | 43 | func randomKeyboardLayout() pb.Keyboard_Layout { 44 | switch rand.Intn(3) { 45 | case 1: 46 | return pb.Keyboard_QWERTY 47 | case 2: 48 | return pb.Keyboard_QWERTZ 49 | default: 50 | return pb.Keyboard_AZERTY 51 | } 52 | } 53 | 54 | func randomScreenResolution() *pb.Screen_Resolution { 55 | height := randomInt(1080, 4320) 56 | width := height * 16 / 9 57 | 58 | resolution := &pb.Screen_Resolution{ 59 | Width: uint32(width), 60 | Height: uint32(height), 61 | } 62 | return resolution 63 | } 64 | 65 | func randomScreenPanel() pb.Screen_Panel { 66 | if rand.Intn(2) == 1 { 67 | return pb.Screen_IPS 68 | } 69 | return pb.Screen_OLED 70 | } 71 | 72 | func randomCPUBrand() string { 73 | return randomStringFromSet("Intel", "AMD") 74 | } 75 | 76 | func randomCPUName(brand string) string { 77 | if brand == "Intel" { 78 | return randomStringFromSet( 79 | "Xeon E-2286M", 80 | "Core i9-9980HK", 81 | "Core i7-9750H", 82 | "Core i5-9400F", 83 | "Core i3-1005G1", 84 | ) 85 | } 86 | 87 | return randomStringFromSet( 88 | "Ryzen 7 PRO 2700U", 89 | "Ryzen 5 PRO 3500U", 90 | "Ryzen 3 PRO 3200GE", 91 | ) 92 | } 93 | 94 | func randomGPUBrand() string { 95 | return randomStringFromSet("Nvidia", "AMD") 96 | } 97 | 98 | func randomGPUName(brand string) string { 99 | if brand == "Nvidia" { 100 | return randomStringFromSet( 101 | "RTX 2060", 102 | "RTX 2070", 103 | "GTX 1660-Ti", 104 | "GTX 1070", 105 | ) 106 | } 107 | 108 | return randomStringFromSet( 109 | "RX 590", 110 | "RX 580", 111 | "RX 5700-XT", 112 | "RX Vega-56", 113 | ) 114 | } 115 | 116 | func randomLaptopBrand() string { 117 | return randomStringFromSet("Apple", "Dell", "Lenovo") 118 | } 119 | 120 | func randomLaptopName(brand string) string { 121 | switch brand { 122 | case "Apple": 123 | return randomStringFromSet("Macbook Air", "Macbook Pro") 124 | case "Dell": 125 | return randomStringFromSet("Latitude", "Vostro", "XPS", "Alienware") 126 | default: 127 | return randomStringFromSet("Thinkpad X1", "Thinkpad P1", "Thinkpad P53") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /serializer/file.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/golang/protobuf/proto" 8 | ) 9 | 10 | // WriteProtobufToJSONFile writes protocol buffer message to JSON file 11 | func WriteProtobufToJSONFile(message proto.Message, filename string) error { 12 | data, err := ProtobufToJSON(message) 13 | if err != nil { 14 | return fmt.Errorf("cannot marshal proto message to JSON: %w", err) 15 | } 16 | 17 | err = ioutil.WriteFile(filename, []byte(data), 0644) 18 | if err != nil { 19 | return fmt.Errorf("cannot write JSON data to file: %w", err) 20 | } 21 | 22 | return nil 23 | } 24 | 25 | // WriteProtobufToBinaryFile writes protocol buffer message to binary file 26 | func WriteProtobufToBinaryFile(message proto.Message, filename string) error { 27 | data, err := proto.Marshal(message) 28 | if err != nil { 29 | return fmt.Errorf("cannot marshal proto message to binary: %w", err) 30 | } 31 | 32 | err = ioutil.WriteFile(filename, data, 0644) 33 | if err != nil { 34 | return fmt.Errorf("cannot write binary data to file: %w", err) 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // ReadProtobufFromBinaryFile reads protocol buffer message from binary file 41 | func ReadProtobufFromBinaryFile(filename string, message proto.Message) error { 42 | data, err := ioutil.ReadFile(filename) 43 | if err != nil { 44 | return fmt.Errorf("cannot read binary data from file: %w", err) 45 | } 46 | 47 | err = proto.Unmarshal(data, message) 48 | if err != nil { 49 | return fmt.Errorf("cannot unmarshal binary to proto message: %w", err) 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /serializer/file_test.go: -------------------------------------------------------------------------------- 1 | package serializer_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/protobuf/proto" 7 | "github.com/stretchr/testify/require" 8 | "gitlab.com/techschool/pcbook/pb" 9 | "gitlab.com/techschool/pcbook/sample" 10 | "gitlab.com/techschool/pcbook/serializer" 11 | ) 12 | 13 | func TestFileSerializer(t *testing.T) { 14 | t.Parallel() 15 | 16 | binaryFile := "../tmp/laptop.bin" 17 | jsonFile := "../tmp/laptop.json" 18 | 19 | laptop1 := sample.NewLaptop() 20 | 21 | err := serializer.WriteProtobufToBinaryFile(laptop1, binaryFile) 22 | require.NoError(t, err) 23 | 24 | err = serializer.WriteProtobufToJSONFile(laptop1, jsonFile) 25 | require.NoError(t, err) 26 | 27 | laptop2 := &pb.Laptop{} 28 | err = serializer.ReadProtobufFromBinaryFile(binaryFile, laptop2) 29 | require.NoError(t, err) 30 | 31 | require.True(t, proto.Equal(laptop1, laptop2)) 32 | } 33 | -------------------------------------------------------------------------------- /serializer/json.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/golang/protobuf/jsonpb" 5 | "github.com/golang/protobuf/proto" 6 | ) 7 | 8 | // ProtobufToJSON converts protocol buffer message to JSON string 9 | func ProtobufToJSON(message proto.Message) (string, error) { 10 | marshaler := jsonpb.Marshaler{ 11 | EnumsAsInts: false, 12 | EmitDefaults: true, 13 | Indent: " ", 14 | OrigName: true, 15 | } 16 | 17 | return marshaler.MarshalToString(message) 18 | } 19 | 20 | // JSONToProtobufMessage converts JSON string to protocol buffer message 21 | func JSONToProtobufMessage(data string, message proto.Message) error { 22 | return jsonpb.UnmarshalString(data, message) 23 | } 24 | -------------------------------------------------------------------------------- /service/auth_interceptor.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/metadata" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | // AuthInterceptor is a server interceptor for authentication and authorization 14 | type AuthInterceptor struct { 15 | jwtManager *JWTManager 16 | accessibleRoles map[string][]string 17 | } 18 | 19 | // NewAuthInterceptor returns a new auth interceptor 20 | func NewAuthInterceptor(jwtManager *JWTManager, accessibleRoles map[string][]string) *AuthInterceptor { 21 | return &AuthInterceptor{jwtManager, accessibleRoles} 22 | } 23 | 24 | // Unary returns a server interceptor function to authenticate and authorize unary RPC 25 | func (interceptor *AuthInterceptor) Unary() grpc.UnaryServerInterceptor { 26 | return func( 27 | ctx context.Context, 28 | req interface{}, 29 | info *grpc.UnaryServerInfo, 30 | handler grpc.UnaryHandler, 31 | ) (interface{}, error) { 32 | log.Println("--> unary interceptor: ", info.FullMethod) 33 | 34 | err := interceptor.authorize(ctx, info.FullMethod) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return handler(ctx, req) 40 | } 41 | } 42 | 43 | // Stream returns a server interceptor function to authenticate and authorize stream RPC 44 | func (interceptor *AuthInterceptor) Stream() grpc.StreamServerInterceptor { 45 | return func( 46 | srv interface{}, 47 | stream grpc.ServerStream, 48 | info *grpc.StreamServerInfo, 49 | handler grpc.StreamHandler, 50 | ) error { 51 | log.Println("--> stream interceptor: ", info.FullMethod) 52 | 53 | err := interceptor.authorize(stream.Context(), info.FullMethod) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return handler(srv, stream) 59 | } 60 | } 61 | 62 | func (interceptor *AuthInterceptor) authorize(ctx context.Context, method string) error { 63 | accessibleRoles, ok := interceptor.accessibleRoles[method] 64 | if !ok { 65 | // everyone can access 66 | return nil 67 | } 68 | 69 | md, ok := metadata.FromIncomingContext(ctx) 70 | if !ok { 71 | return status.Errorf(codes.Unauthenticated, "metadata is not provided") 72 | } 73 | 74 | values := md["authorization"] 75 | if len(values) == 0 { 76 | return status.Errorf(codes.Unauthenticated, "authorization token is not provided") 77 | } 78 | 79 | accessToken := values[0] 80 | claims, err := interceptor.jwtManager.Verify(accessToken) 81 | if err != nil { 82 | return status.Errorf(codes.Unauthenticated, "access token is invalid: %v", err) 83 | } 84 | 85 | for _, role := range accessibleRoles { 86 | if role == claims.Role { 87 | return nil 88 | } 89 | } 90 | 91 | return status.Error(codes.PermissionDenied, "no permission to access this RPC") 92 | } 93 | -------------------------------------------------------------------------------- /service/auth_server.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "gitlab.com/techschool/pcbook/pb" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | // AuthServer is the server for authentication 12 | type AuthServer struct { 13 | pb.UnimplementedAuthServiceServer 14 | userStore UserStore 15 | jwtManager *JWTManager 16 | } 17 | 18 | // NewAuthServer returns a new auth server 19 | func NewAuthServer(userStore UserStore, jwtManager *JWTManager) pb.AuthServiceServer { 20 | return &AuthServer{userStore: userStore, jwtManager: jwtManager} 21 | } 22 | 23 | // Login is a unary RPC to login user 24 | func (server *AuthServer) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { 25 | user, err := server.userStore.Find(req.GetUsername()) 26 | if err != nil { 27 | return nil, status.Errorf(codes.Internal, "cannot find user: %v", err) 28 | } 29 | 30 | if user == nil || !user.IsCorrectPassword(req.GetPassword()) { 31 | return nil, status.Errorf(codes.NotFound, "incorrect username/password") 32 | } 33 | 34 | token, err := server.jwtManager.Generate(user) 35 | if err != nil { 36 | return nil, status.Errorf(codes.Internal, "cannot generate access token") 37 | } 38 | 39 | res := &pb.LoginResponse{AccessToken: token} 40 | return res, nil 41 | } 42 | -------------------------------------------------------------------------------- /service/image_store.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "sync" 8 | 9 | "github.com/google/uuid" 10 | ) 11 | 12 | // ImageStore is an interface to store laptop images 13 | type ImageStore interface { 14 | // Save saves a new laptop image to the store 15 | Save(laptopID string, imageType string, imageData bytes.Buffer) (string, error) 16 | } 17 | 18 | // DiskImageStore stores image on disk, and its info on memory 19 | type DiskImageStore struct { 20 | mutex sync.RWMutex 21 | imageFolder string 22 | images map[string]*ImageInfo 23 | } 24 | 25 | // ImageInfo contains information of the laptop image 26 | type ImageInfo struct { 27 | LaptopID string 28 | Type string 29 | Path string 30 | } 31 | 32 | // NewDiskImageStore returns a new DiskImageStore 33 | func NewDiskImageStore(imageFolder string) *DiskImageStore { 34 | return &DiskImageStore{ 35 | imageFolder: imageFolder, 36 | images: make(map[string]*ImageInfo), 37 | } 38 | } 39 | 40 | // Save adds a new image to a laptop 41 | func (store *DiskImageStore) Save( 42 | laptopID string, 43 | imageType string, 44 | imageData bytes.Buffer, 45 | ) (string, error) { 46 | imageID, err := uuid.NewRandom() 47 | if err != nil { 48 | return "", fmt.Errorf("cannot generate image id: %w", err) 49 | } 50 | 51 | imagePath := fmt.Sprintf("%s/%s%s", store.imageFolder, imageID, imageType) 52 | 53 | file, err := os.Create(imagePath) 54 | if err != nil { 55 | return "", fmt.Errorf("cannot create image file: %w", err) 56 | } 57 | 58 | _, err = imageData.WriteTo(file) 59 | if err != nil { 60 | return "", fmt.Errorf("cannot write image to file: %w", err) 61 | } 62 | 63 | store.mutex.Lock() 64 | defer store.mutex.Unlock() 65 | 66 | store.images[imageID.String()] = &ImageInfo{ 67 | LaptopID: laptopID, 68 | Type: imageType, 69 | Path: imagePath, 70 | } 71 | 72 | return imageID.String(), nil 73 | } 74 | -------------------------------------------------------------------------------- /service/jwt_manager.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/dgrijalva/jwt-go" 8 | ) 9 | 10 | // JWTManager is a JSON web token manager 11 | type JWTManager struct { 12 | secretKey string 13 | tokenDuration time.Duration 14 | } 15 | 16 | // UserClaims is a custom JWT claims that contains some user's information 17 | type UserClaims struct { 18 | jwt.StandardClaims 19 | Username string `json:"username"` 20 | Role string `json:"role"` 21 | } 22 | 23 | // NewJWTManager returns a new JWT manager 24 | func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager { 25 | return &JWTManager{secretKey, tokenDuration} 26 | } 27 | 28 | // Generate generates and signs a new token for a user 29 | func (manager *JWTManager) Generate(user *User) (string, error) { 30 | claims := UserClaims{ 31 | StandardClaims: jwt.StandardClaims{ 32 | ExpiresAt: time.Now().Add(manager.tokenDuration).Unix(), 33 | }, 34 | Username: user.Username, 35 | Role: user.Role, 36 | } 37 | 38 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 39 | return token.SignedString([]byte(manager.secretKey)) 40 | } 41 | 42 | // Verify verifies the access token string and return a user claim if the token is valid 43 | func (manager *JWTManager) Verify(accessToken string) (*UserClaims, error) { 44 | token, err := jwt.ParseWithClaims( 45 | accessToken, 46 | &UserClaims{}, 47 | func(token *jwt.Token) (interface{}, error) { 48 | _, ok := token.Method.(*jwt.SigningMethodHMAC) 49 | if !ok { 50 | return nil, fmt.Errorf("unexpected token signing method") 51 | } 52 | 53 | return []byte(manager.secretKey), nil 54 | }, 55 | ) 56 | 57 | if err != nil { 58 | return nil, fmt.Errorf("invalid token: %w", err) 59 | } 60 | 61 | claims, ok := token.Claims.(*UserClaims) 62 | if !ok { 63 | return nil, fmt.Errorf("invalid token claims") 64 | } 65 | 66 | return claims, nil 67 | } 68 | -------------------------------------------------------------------------------- /service/laptop_client_test.go: -------------------------------------------------------------------------------- 1 | package service_test 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | "gitlab.com/techschool/pcbook/pb" 15 | "gitlab.com/techschool/pcbook/sample" 16 | "gitlab.com/techschool/pcbook/serializer" 17 | "gitlab.com/techschool/pcbook/service" 18 | "google.golang.org/grpc" 19 | ) 20 | 21 | func TestClientCreateLaptop(t *testing.T) { 22 | t.Parallel() 23 | 24 | laptopStore := service.NewInMemoryLaptopStore() 25 | serverAddress := startTestLaptopServer(t, laptopStore, nil, nil) 26 | laptopClient := newTestLaptopClient(t, serverAddress) 27 | 28 | laptop := sample.NewLaptop() 29 | expectedID := laptop.Id 30 | req := &pb.CreateLaptopRequest{ 31 | Laptop: laptop, 32 | } 33 | 34 | res, err := laptopClient.CreateLaptop(context.Background(), req) 35 | require.NoError(t, err) 36 | require.NotNil(t, res) 37 | require.Equal(t, expectedID, res.Id) 38 | 39 | // check that the laptop is saved to the store 40 | other, err := laptopStore.Find(res.Id) 41 | require.NoError(t, err) 42 | require.NotNil(t, other) 43 | 44 | // check that the saved laptop is the same as the one we send 45 | requireSameLaptop(t, laptop, other) 46 | } 47 | 48 | func TestClientSearchLaptop(t *testing.T) { 49 | t.Parallel() 50 | 51 | filter := &pb.Filter{ 52 | MaxPriceUsd: 2000, 53 | MinCpuCores: 4, 54 | MinCpuGhz: 2.2, 55 | MinRam: &pb.Memory{Value: 8, Unit: pb.Memory_GIGABYTE}, 56 | } 57 | 58 | laptopStore := service.NewInMemoryLaptopStore() 59 | expectedIDs := make(map[string]bool) 60 | 61 | for i := 0; i < 6; i++ { 62 | laptop := sample.NewLaptop() 63 | 64 | switch i { 65 | case 0: 66 | laptop.PriceUsd = 2500 67 | case 1: 68 | laptop.Cpu.NumberCores = 2 69 | case 2: 70 | laptop.Cpu.MinGhz = 2.0 71 | case 3: 72 | laptop.Ram = &pb.Memory{Value: 4096, Unit: pb.Memory_MEGABYTE} 73 | case 4: 74 | laptop.PriceUsd = 1999 75 | laptop.Cpu.NumberCores = 4 76 | laptop.Cpu.MinGhz = 2.5 77 | laptop.Cpu.MaxGhz = laptop.Cpu.MinGhz + 2.0 78 | laptop.Ram = &pb.Memory{Value: 16, Unit: pb.Memory_GIGABYTE} 79 | expectedIDs[laptop.Id] = true 80 | case 5: 81 | laptop.PriceUsd = 2000 82 | laptop.Cpu.NumberCores = 6 83 | laptop.Cpu.MinGhz = 2.8 84 | laptop.Cpu.MaxGhz = laptop.Cpu.MinGhz + 2.0 85 | laptop.Ram = &pb.Memory{Value: 64, Unit: pb.Memory_GIGABYTE} 86 | expectedIDs[laptop.Id] = true 87 | } 88 | 89 | err := laptopStore.Save(laptop) 90 | require.NoError(t, err) 91 | } 92 | 93 | serverAddress := startTestLaptopServer(t, laptopStore, nil, nil) 94 | laptopClient := newTestLaptopClient(t, serverAddress) 95 | 96 | req := &pb.SearchLaptopRequest{Filter: filter} 97 | stream, err := laptopClient.SearchLaptop(context.Background(), req) 98 | require.NoError(t, err) 99 | 100 | found := 0 101 | for { 102 | res, err := stream.Recv() 103 | if err == io.EOF { 104 | break 105 | } 106 | 107 | require.NoError(t, err) 108 | require.Contains(t, expectedIDs, res.GetLaptop().GetId()) 109 | 110 | found += 1 111 | } 112 | 113 | require.Equal(t, len(expectedIDs), found) 114 | } 115 | 116 | func TestClientUploadImage(t *testing.T) { 117 | t.Parallel() 118 | 119 | testImageFolder := "../tmp" 120 | 121 | laptopStore := service.NewInMemoryLaptopStore() 122 | imageStore := service.NewDiskImageStore(testImageFolder) 123 | 124 | laptop := sample.NewLaptop() 125 | err := laptopStore.Save(laptop) 126 | require.NoError(t, err) 127 | 128 | serverAddress := startTestLaptopServer(t, laptopStore, imageStore, nil) 129 | laptopClient := newTestLaptopClient(t, serverAddress) 130 | 131 | imagePath := fmt.Sprintf("%s/laptop.jpg", testImageFolder) 132 | file, err := os.Open(imagePath) 133 | require.NoError(t, err) 134 | defer file.Close() 135 | 136 | stream, err := laptopClient.UploadImage(context.Background()) 137 | require.NoError(t, err) 138 | 139 | imageType := filepath.Ext(imagePath) 140 | req := &pb.UploadImageRequest{ 141 | Data: &pb.UploadImageRequest_Info{ 142 | Info: &pb.ImageInfo{ 143 | LaptopId: laptop.GetId(), 144 | ImageType: imageType, 145 | }, 146 | }, 147 | } 148 | 149 | err = stream.Send(req) 150 | require.NoError(t, err) 151 | 152 | reader := bufio.NewReader(file) 153 | buffer := make([]byte, 1024) 154 | size := 0 155 | 156 | for { 157 | n, err := reader.Read(buffer) 158 | if err == io.EOF { 159 | break 160 | } 161 | 162 | require.NoError(t, err) 163 | size += n 164 | 165 | req := &pb.UploadImageRequest{ 166 | Data: &pb.UploadImageRequest_ChunkData{ 167 | ChunkData: buffer[:n], 168 | }, 169 | } 170 | 171 | err = stream.Send(req) 172 | require.NoError(t, err) 173 | } 174 | 175 | res, err := stream.CloseAndRecv() 176 | require.NoError(t, err) 177 | require.NotZero(t, res.GetId()) 178 | require.EqualValues(t, size, res.GetSize()) 179 | 180 | savedImagePath := fmt.Sprintf("%s/%s%s", testImageFolder, res.GetId(), imageType) 181 | require.FileExists(t, savedImagePath) 182 | require.NoError(t, os.Remove(savedImagePath)) 183 | } 184 | 185 | func TestClientRateLaptop(t *testing.T) { 186 | t.Parallel() 187 | 188 | laptopStore := service.NewInMemoryLaptopStore() 189 | ratingStore := service.NewInMemoryRatingStore() 190 | 191 | laptop := sample.NewLaptop() 192 | err := laptopStore.Save(laptop) 193 | require.NoError(t, err) 194 | 195 | serverAddress := startTestLaptopServer(t, laptopStore, nil, ratingStore) 196 | laptopClient := newTestLaptopClient(t, serverAddress) 197 | 198 | stream, err := laptopClient.RateLaptop(context.Background()) 199 | require.NoError(t, err) 200 | 201 | scores := []float64{8, 7.5, 10} 202 | averages := []float64{8, 7.75, 8.5} 203 | 204 | n := len(scores) 205 | for i := 0; i < n; i++ { 206 | req := &pb.RateLaptopRequest{ 207 | LaptopId: laptop.GetId(), 208 | Score: scores[i], 209 | } 210 | 211 | err := stream.Send(req) 212 | require.NoError(t, err) 213 | } 214 | 215 | err = stream.CloseSend() 216 | require.NoError(t, err) 217 | 218 | for idx := 0; ; idx++ { 219 | res, err := stream.Recv() 220 | if err == io.EOF { 221 | require.Equal(t, n, idx) 222 | return 223 | } 224 | 225 | require.NoError(t, err) 226 | require.Equal(t, laptop.GetId(), res.GetLaptopId()) 227 | require.Equal(t, uint32(idx+1), res.GetRatedCount()) 228 | require.Equal(t, averages[idx], res.GetAverageScore()) 229 | } 230 | } 231 | 232 | func startTestLaptopServer(t *testing.T, laptopStore service.LaptopStore, imageStore service.ImageStore, ratingStore service.RatingStore) string { 233 | laptopServer := service.NewLaptopServer(laptopStore, imageStore, ratingStore) 234 | 235 | grpcServer := grpc.NewServer() 236 | pb.RegisterLaptopServiceServer(grpcServer, laptopServer) 237 | 238 | listener, err := net.Listen("tcp", ":0") // random available port 239 | require.NoError(t, err) 240 | 241 | go grpcServer.Serve(listener) 242 | 243 | return listener.Addr().String() 244 | } 245 | 246 | func newTestLaptopClient(t *testing.T, serverAddress string) pb.LaptopServiceClient { 247 | conn, err := grpc.Dial(serverAddress, grpc.WithInsecure()) 248 | require.NoError(t, err) 249 | return pb.NewLaptopServiceClient(conn) 250 | } 251 | 252 | func requireSameLaptop(t *testing.T, laptop1 *pb.Laptop, laptop2 *pb.Laptop) { 253 | json1, err := serializer.ProtobufToJSON(laptop1) 254 | require.NoError(t, err) 255 | 256 | json2, err := serializer.ProtobufToJSON(laptop2) 257 | require.NoError(t, err) 258 | 259 | require.Equal(t, json1, json2) 260 | } 261 | -------------------------------------------------------------------------------- /service/laptop_server.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "log" 9 | 10 | "github.com/google/uuid" 11 | "gitlab.com/techschool/pcbook/pb" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | const maxImageSize = 1 << 20 17 | 18 | // LaptopServer is the server that provides laptop services 19 | type LaptopServer struct { 20 | pb.UnimplementedLaptopServiceServer 21 | laptopStore LaptopStore 22 | imageStore ImageStore 23 | ratingStore RatingStore 24 | } 25 | 26 | // NewLaptopServer returns a new LaptopServer 27 | func NewLaptopServer(laptopStore LaptopStore, imageStore ImageStore, ratingStore RatingStore) *LaptopServer { 28 | return &LaptopServer{ 29 | laptopStore: laptopStore, 30 | imageStore: imageStore, 31 | ratingStore: ratingStore, 32 | } 33 | } 34 | 35 | // CreateLaptop is a unary RPC to create a new laptop 36 | func (server *LaptopServer) CreateLaptop( 37 | ctx context.Context, 38 | req *pb.CreateLaptopRequest, 39 | ) (*pb.CreateLaptopResponse, error) { 40 | laptop := req.GetLaptop() 41 | log.Printf("receive a create-laptop request with id: %s", laptop.Id) 42 | 43 | if len(laptop.Id) > 0 { 44 | // check if it's a valid UUID 45 | _, err := uuid.Parse(laptop.Id) 46 | if err != nil { 47 | return nil, status.Errorf(codes.InvalidArgument, "laptop ID is not a valid UUID: %v", err) 48 | } 49 | } else { 50 | id, err := uuid.NewRandom() 51 | if err != nil { 52 | return nil, status.Errorf(codes.Internal, "cannot generate a new laptop ID: %v", err) 53 | } 54 | laptop.Id = id.String() 55 | } 56 | 57 | // some heavy processing 58 | // time.Sleep(6 * time.Second) 59 | 60 | if err := contextError(ctx); err != nil { 61 | return nil, err 62 | } 63 | 64 | // save the laptop to store 65 | err := server.laptopStore.Save(laptop) 66 | if err != nil { 67 | code := codes.Internal 68 | if errors.Is(err, ErrAlreadyExists) { 69 | code = codes.AlreadyExists 70 | } 71 | 72 | return nil, status.Errorf(code, "cannot save laptop to the store: %v", err) 73 | } 74 | 75 | log.Printf("saved laptop with id: %s", laptop.Id) 76 | 77 | res := &pb.CreateLaptopResponse{ 78 | Id: laptop.Id, 79 | } 80 | return res, nil 81 | } 82 | 83 | // SearchLaptop is a server-streaming RPC to search for laptops 84 | func (server *LaptopServer) SearchLaptop( 85 | req *pb.SearchLaptopRequest, 86 | stream pb.LaptopService_SearchLaptopServer, 87 | ) error { 88 | filter := req.GetFilter() 89 | log.Printf("receive a search-laptop request with filter: %v", filter) 90 | 91 | err := server.laptopStore.Search( 92 | stream.Context(), 93 | filter, 94 | func(laptop *pb.Laptop) error { 95 | res := &pb.SearchLaptopResponse{Laptop: laptop} 96 | err := stream.Send(res) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | log.Printf("sent laptop with id: %s", laptop.GetId()) 102 | return nil 103 | }, 104 | ) 105 | 106 | if err != nil { 107 | return status.Errorf(codes.Internal, "unexpected error: %v", err) 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // UploadImage is a client-streaming RPC to upload a laptop image 114 | func (server *LaptopServer) UploadImage(stream pb.LaptopService_UploadImageServer) error { 115 | req, err := stream.Recv() 116 | if err != nil { 117 | return logError(status.Errorf(codes.Unknown, "cannot receive image info")) 118 | } 119 | 120 | laptopID := req.GetInfo().GetLaptopId() 121 | imageType := req.GetInfo().GetImageType() 122 | log.Printf("receive an upload-image request for laptop %s with image type %s", laptopID, imageType) 123 | 124 | laptop, err := server.laptopStore.Find(laptopID) 125 | if err != nil { 126 | return logError(status.Errorf(codes.Internal, "cannot find laptop: %v", err)) 127 | } 128 | if laptop == nil { 129 | return logError(status.Errorf(codes.InvalidArgument, "laptop id %s doesn't exist", laptopID)) 130 | } 131 | 132 | imageData := bytes.Buffer{} 133 | imageSize := 0 134 | 135 | for { 136 | err := contextError(stream.Context()) 137 | if err != nil { 138 | return err 139 | } 140 | 141 | log.Print("waiting to receive more data") 142 | 143 | req, err := stream.Recv() 144 | if err == io.EOF { 145 | log.Print("no more data") 146 | break 147 | } 148 | if err != nil { 149 | return logError(status.Errorf(codes.Unknown, "cannot receive chunk data: %v", err)) 150 | } 151 | 152 | chunk := req.GetChunkData() 153 | size := len(chunk) 154 | 155 | log.Printf("received a chunk with size: %d", size) 156 | 157 | imageSize += size 158 | if imageSize > maxImageSize { 159 | return logError(status.Errorf(codes.InvalidArgument, "image is too large: %d > %d", imageSize, maxImageSize)) 160 | } 161 | 162 | // write slowly 163 | // time.Sleep(time.Second) 164 | 165 | _, err = imageData.Write(chunk) 166 | if err != nil { 167 | return logError(status.Errorf(codes.Internal, "cannot write chunk data: %v", err)) 168 | } 169 | } 170 | 171 | imageID, err := server.imageStore.Save(laptopID, imageType, imageData) 172 | if err != nil { 173 | return logError(status.Errorf(codes.Internal, "cannot save image to the store: %v", err)) 174 | } 175 | 176 | res := &pb.UploadImageResponse{ 177 | Id: imageID, 178 | Size: uint32(imageSize), 179 | } 180 | 181 | err = stream.SendAndClose(res) 182 | if err != nil { 183 | return logError(status.Errorf(codes.Unknown, "cannot send response: %v", err)) 184 | } 185 | 186 | log.Printf("saved image with id: %s, size: %d", imageID, imageSize) 187 | return nil 188 | } 189 | 190 | // RateLaptop is a bidirectional-streaming RPC that allows client to rate a stream of laptops 191 | // with a score, and returns a stream of average score for each of them 192 | func (server *LaptopServer) RateLaptop(stream pb.LaptopService_RateLaptopServer) error { 193 | for { 194 | err := contextError(stream.Context()) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | req, err := stream.Recv() 200 | if err == io.EOF { 201 | log.Print("no more data") 202 | break 203 | } 204 | if err != nil { 205 | return logError(status.Errorf(codes.Unknown, "cannot receive stream request: %v", err)) 206 | } 207 | 208 | laptopID := req.GetLaptopId() 209 | score := req.GetScore() 210 | 211 | log.Printf("received a rate-laptop request: id = %s, score = %.2f", laptopID, score) 212 | 213 | found, err := server.laptopStore.Find(laptopID) 214 | if err != nil { 215 | return logError(status.Errorf(codes.Internal, "cannot find laptop: %v", err)) 216 | } 217 | if found == nil { 218 | return logError(status.Errorf(codes.NotFound, "laptopID %s is not found", laptopID)) 219 | } 220 | 221 | rating, err := server.ratingStore.Add(laptopID, score) 222 | if err != nil { 223 | return logError(status.Errorf(codes.Internal, "cannot add rating to the store: %v", err)) 224 | } 225 | 226 | res := &pb.RateLaptopResponse{ 227 | LaptopId: laptopID, 228 | RatedCount: rating.Count, 229 | AverageScore: rating.Sum / float64(rating.Count), 230 | } 231 | 232 | err = stream.Send(res) 233 | if err != nil { 234 | return logError(status.Errorf(codes.Unknown, "cannot send stream response: %v", err)) 235 | } 236 | } 237 | 238 | return nil 239 | } 240 | 241 | func contextError(ctx context.Context) error { 242 | switch ctx.Err() { 243 | case context.Canceled: 244 | return logError(status.Error(codes.Canceled, "request is canceled")) 245 | case context.DeadlineExceeded: 246 | return logError(status.Error(codes.DeadlineExceeded, "deadline is exceeded")) 247 | default: 248 | return nil 249 | } 250 | } 251 | 252 | func logError(err error) error { 253 | if err != nil { 254 | log.Print(err) 255 | } 256 | return err 257 | } 258 | -------------------------------------------------------------------------------- /service/laptop_server_test.go: -------------------------------------------------------------------------------- 1 | package service_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "gitlab.com/techschool/pcbook/pb" 9 | "gitlab.com/techschool/pcbook/sample" 10 | "gitlab.com/techschool/pcbook/service" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | func TestServerCreateLaptop(t *testing.T) { 16 | t.Parallel() 17 | 18 | laptopNoID := sample.NewLaptop() 19 | laptopNoID.Id = "" 20 | 21 | laptopInvalidID := sample.NewLaptop() 22 | laptopInvalidID.Id = "invalid-uuid" 23 | 24 | laptopDuplicateID := sample.NewLaptop() 25 | storeDuplicateID := service.NewInMemoryLaptopStore() 26 | err := storeDuplicateID.Save(laptopDuplicateID) 27 | require.Nil(t, err) 28 | 29 | testCases := []struct { 30 | name string 31 | laptop *pb.Laptop 32 | store service.LaptopStore 33 | code codes.Code 34 | }{ 35 | { 36 | name: "success_with_id", 37 | laptop: sample.NewLaptop(), 38 | store: service.NewInMemoryLaptopStore(), 39 | code: codes.OK, 40 | }, 41 | { 42 | name: "success_no_id", 43 | laptop: laptopNoID, 44 | store: service.NewInMemoryLaptopStore(), 45 | code: codes.OK, 46 | }, 47 | { 48 | name: "failure_invalid_id", 49 | laptop: laptopInvalidID, 50 | store: service.NewInMemoryLaptopStore(), 51 | code: codes.InvalidArgument, 52 | }, 53 | { 54 | name: "failure_duplicate_id", 55 | laptop: laptopDuplicateID, 56 | store: storeDuplicateID, 57 | code: codes.AlreadyExists, 58 | }, 59 | } 60 | 61 | for i := range testCases { 62 | tc := testCases[i] 63 | 64 | t.Run(tc.name, func(t *testing.T) { 65 | t.Parallel() 66 | 67 | req := &pb.CreateLaptopRequest{ 68 | Laptop: tc.laptop, 69 | } 70 | 71 | server := service.NewLaptopServer(tc.store, nil, nil) 72 | res, err := server.CreateLaptop(context.Background(), req) 73 | if tc.code == codes.OK { 74 | require.NoError(t, err) 75 | require.NotNil(t, res) 76 | require.NotEmpty(t, res.Id) 77 | if len(tc.laptop.Id) > 0 { 78 | require.Equal(t, tc.laptop.Id, res.Id) 79 | } 80 | } else { 81 | require.Error(t, err) 82 | require.Nil(t, res) 83 | st, ok := status.FromError(err) 84 | require.True(t, ok) 85 | require.Equal(t, tc.code, st.Code()) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /service/laptop_store.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "sync" 9 | 10 | "github.com/jinzhu/copier" 11 | "gitlab.com/techschool/pcbook/pb" 12 | ) 13 | 14 | // ErrAlreadyExists is returned when a record with the same ID already exists in the store 15 | var ErrAlreadyExists = errors.New("record already exists") 16 | 17 | // LaptopStore is an interface to store laptop 18 | type LaptopStore interface { 19 | // Save saves the laptop to the store 20 | Save(laptop *pb.Laptop) error 21 | // Find finds a laptop by ID 22 | Find(id string) (*pb.Laptop, error) 23 | // Search searches for laptops with filter, returns one by one via the found function 24 | Search(ctx context.Context, filter *pb.Filter, found func(laptop *pb.Laptop) error) error 25 | } 26 | 27 | // InMemoryLaptopStore stores laptop in memory 28 | type InMemoryLaptopStore struct { 29 | mutex sync.RWMutex 30 | data map[string]*pb.Laptop 31 | } 32 | 33 | // NewInMemoryLaptopStore returns a new InMemoryLaptopStore 34 | func NewInMemoryLaptopStore() *InMemoryLaptopStore { 35 | return &InMemoryLaptopStore{ 36 | data: make(map[string]*pb.Laptop), 37 | } 38 | } 39 | 40 | // Save saves the laptop to the store 41 | func (store *InMemoryLaptopStore) Save(laptop *pb.Laptop) error { 42 | store.mutex.Lock() 43 | defer store.mutex.Unlock() 44 | 45 | if store.data[laptop.Id] != nil { 46 | return ErrAlreadyExists 47 | } 48 | 49 | other, err := deepCopy(laptop) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | store.data[other.Id] = other 55 | return nil 56 | } 57 | 58 | // Find finds a laptop by ID 59 | func (store *InMemoryLaptopStore) Find(id string) (*pb.Laptop, error) { 60 | store.mutex.RLock() 61 | defer store.mutex.RUnlock() 62 | 63 | laptop := store.data[id] 64 | if laptop == nil { 65 | return nil, nil 66 | } 67 | 68 | return deepCopy(laptop) 69 | } 70 | 71 | // Search searches for laptops with filter, returns one by one via the found function 72 | func (store *InMemoryLaptopStore) Search( 73 | ctx context.Context, 74 | filter *pb.Filter, 75 | found func(laptop *pb.Laptop) error, 76 | ) error { 77 | store.mutex.RLock() 78 | defer store.mutex.RUnlock() 79 | 80 | for _, laptop := range store.data { 81 | if ctx.Err() == context.Canceled || ctx.Err() == context.DeadlineExceeded { 82 | log.Print("context is cancelled") 83 | return nil 84 | } 85 | 86 | // time.Sleep(time.Second) 87 | // log.Print("checking laptop id: ", laptop.GetId()) 88 | 89 | if isQualified(filter, laptop) { 90 | other, err := deepCopy(laptop) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | err = found(other) 96 | if err != nil { 97 | return err 98 | } 99 | } 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func isQualified(filter *pb.Filter, laptop *pb.Laptop) bool { 106 | if laptop.GetPriceUsd() > filter.GetMaxPriceUsd() { 107 | return false 108 | } 109 | 110 | if laptop.GetCpu().GetNumberCores() < filter.GetMinCpuCores() { 111 | return false 112 | } 113 | 114 | if laptop.GetCpu().GetMinGhz() < filter.GetMinCpuGhz() { 115 | return false 116 | } 117 | 118 | if toBit(laptop.GetRam()) < toBit(filter.GetMinRam()) { 119 | return false 120 | } 121 | 122 | return true 123 | } 124 | 125 | func toBit(memory *pb.Memory) uint64 { 126 | value := memory.GetValue() 127 | 128 | switch memory.GetUnit() { 129 | case pb.Memory_BIT: 130 | return value 131 | case pb.Memory_BYTE: 132 | return value << 3 // 8 = 2^3 133 | case pb.Memory_KILOBYTE: 134 | return value << 13 // 1024 * 8 = 2^10 * 2^3 = 2^13 135 | case pb.Memory_MEGABYTE: 136 | return value << 23 137 | case pb.Memory_GIGABYTE: 138 | return value << 33 139 | case pb.Memory_TERABYTE: 140 | return value << 43 141 | default: 142 | return 0 143 | } 144 | } 145 | 146 | func deepCopy(laptop *pb.Laptop) (*pb.Laptop, error) { 147 | other := &pb.Laptop{} 148 | 149 | err := copier.Copy(other, laptop) 150 | if err != nil { 151 | return nil, fmt.Errorf("cannot copy laptop data: %w", err) 152 | } 153 | 154 | return other, nil 155 | } 156 | -------------------------------------------------------------------------------- /service/rating_store.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "sync" 4 | 5 | // RatingStore is an interface to store laptop ratings 6 | type RatingStore interface { 7 | // Add adds a new laptop score to the store and returns its rating 8 | Add(laptopID string, score float64) (*Rating, error) 9 | } 10 | 11 | // Rating contains the rating information of a laptop 12 | type Rating struct { 13 | Count uint32 14 | Sum float64 15 | } 16 | 17 | // InMemoryRatingStore stores laptop ratings in memory 18 | type InMemoryRatingStore struct { 19 | mutex sync.RWMutex 20 | rating map[string]*Rating 21 | } 22 | 23 | // NewInMemoryRatingStore returns a new InMemoryRatingStore 24 | func NewInMemoryRatingStore() *InMemoryRatingStore { 25 | return &InMemoryRatingStore{ 26 | rating: make(map[string]*Rating), 27 | } 28 | } 29 | 30 | // Add adds a new laptop score to the store and returns its rating 31 | func (store *InMemoryRatingStore) Add(laptopID string, score float64) (*Rating, error) { 32 | store.mutex.Lock() 33 | defer store.mutex.Unlock() 34 | 35 | rating := store.rating[laptopID] 36 | if rating == nil { 37 | rating = &Rating{ 38 | Count: 1, 39 | Sum: score, 40 | } 41 | } else { 42 | rating.Count++ 43 | rating.Sum += score 44 | } 45 | 46 | store.rating[laptopID] = rating 47 | return rating, nil 48 | } 49 | -------------------------------------------------------------------------------- /service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/crypto/bcrypt" 7 | ) 8 | 9 | // User contains user's information 10 | type User struct { 11 | Username string 12 | HashedPassword string 13 | Role string 14 | } 15 | 16 | // NewUser returns a new user 17 | func NewUser(username string, password string, role string) (*User, error) { 18 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 19 | if err != nil { 20 | return nil, fmt.Errorf("cannot hash password: %w", err) 21 | } 22 | 23 | user := &User{ 24 | Username: username, 25 | HashedPassword: string(hashedPassword), 26 | Role: role, 27 | } 28 | 29 | return user, nil 30 | } 31 | 32 | // IsCorrectPassword checks if the provided password is correct or not 33 | func (user *User) IsCorrectPassword(password string) bool { 34 | err := bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(password)) 35 | return err == nil 36 | } 37 | 38 | // Clone returns a clone of this user 39 | func (user *User) Clone() *User { 40 | return &User{ 41 | Username: user.Username, 42 | HashedPassword: user.HashedPassword, 43 | Role: user.Role, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /service/user_store.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "sync" 4 | 5 | // UserStore is an interface to store users 6 | type UserStore interface { 7 | // Save saves a user to the store 8 | Save(user *User) error 9 | // Find finds a user by username 10 | Find(username string) (*User, error) 11 | } 12 | 13 | // InMemoryUserStore stores users in memory 14 | type InMemoryUserStore struct { 15 | mutex sync.RWMutex 16 | users map[string]*User 17 | } 18 | 19 | // NewInMemoryUserStore returns a new in-memory user store 20 | func NewInMemoryUserStore() *InMemoryUserStore { 21 | return &InMemoryUserStore{ 22 | users: make(map[string]*User), 23 | } 24 | } 25 | 26 | // Save saves a user to the store 27 | func (store *InMemoryUserStore) Save(user *User) error { 28 | store.mutex.Lock() 29 | defer store.mutex.Unlock() 30 | 31 | if store.users[user.Username] != nil { 32 | return ErrAlreadyExists 33 | } 34 | 35 | store.users[user.Username] = user.Clone() 36 | return nil 37 | } 38 | 39 | // Find finds a user by username 40 | func (store *InMemoryUserStore) Find(username string) (*User, error) { 41 | store.mutex.RLock() 42 | defer store.mutex.RUnlock() 43 | 44 | user := store.users[username] 45 | if user == nil { 46 | return nil, nil 47 | } 48 | 49 | return user.Clone(), nil 50 | } 51 | -------------------------------------------------------------------------------- /swagger/auth_service.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "auth_service.proto", 5 | "version": "version not set" 6 | }, 7 | "tags": [ 8 | { 9 | "name": "AuthService" 10 | } 11 | ], 12 | "consumes": [ 13 | "application/json" 14 | ], 15 | "produces": [ 16 | "application/json" 17 | ], 18 | "paths": { 19 | "/v1/auth/login": { 20 | "post": { 21 | "operationId": "AuthService_Login", 22 | "responses": { 23 | "200": { 24 | "description": "A successful response.", 25 | "schema": { 26 | "$ref": "#/definitions/pcbookLoginResponse" 27 | } 28 | }, 29 | "default": { 30 | "description": "An unexpected error response.", 31 | "schema": { 32 | "$ref": "#/definitions/rpcStatus" 33 | } 34 | } 35 | }, 36 | "parameters": [ 37 | { 38 | "name": "body", 39 | "in": "body", 40 | "required": true, 41 | "schema": { 42 | "$ref": "#/definitions/pcbookLoginRequest" 43 | } 44 | } 45 | ], 46 | "tags": [ 47 | "AuthService" 48 | ] 49 | } 50 | } 51 | }, 52 | "definitions": { 53 | "pcbookLoginRequest": { 54 | "type": "object", 55 | "properties": { 56 | "username": { 57 | "type": "string" 58 | }, 59 | "password": { 60 | "type": "string" 61 | } 62 | } 63 | }, 64 | "pcbookLoginResponse": { 65 | "type": "object", 66 | "properties": { 67 | "accessToken": { 68 | "type": "string" 69 | } 70 | } 71 | }, 72 | "protobufAny": { 73 | "type": "object", 74 | "properties": { 75 | "typeUrl": { 76 | "type": "string" 77 | }, 78 | "value": { 79 | "type": "string", 80 | "format": "byte" 81 | } 82 | } 83 | }, 84 | "rpcStatus": { 85 | "type": "object", 86 | "properties": { 87 | "code": { 88 | "type": "integer", 89 | "format": "int32" 90 | }, 91 | "message": { 92 | "type": "string" 93 | }, 94 | "details": { 95 | "type": "array", 96 | "items": { 97 | "$ref": "#/definitions/protobufAny" 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /swagger/filter_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "filter_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/keyboard_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "keyboard_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/laptop_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "laptop_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/memory_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "memory_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/processor_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "processor_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/screen_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "screen_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /swagger/storage_message.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "storage_message.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tmp/laptop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techschool/pcbook-go/aa74912a982ca43eb44163de853554a255f66c25/tmp/laptop.bin -------------------------------------------------------------------------------- /tmp/laptop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techschool/pcbook-go/aa74912a982ca43eb44163de853554a255f66c25/tmp/laptop.jpg -------------------------------------------------------------------------------- /tmp/laptop.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "22901c4c-2f34-43b6-8190-e3a57daa1a41", 3 | "brand": "Apple", 4 | "name": "Macbook Air", 5 | "cpu": { 6 | "brand": "AMD", 7 | "name": "Ryzen 7 PRO 2700U", 8 | "number_cores": 2, 9 | "number_threads": 6, 10 | "min_ghz": 2.678712980507639, 11 | "max_ghz": 4.812780681764419 12 | }, 13 | "ram": { 14 | "value": "55", 15 | "unit": "GIGABYTE" 16 | }, 17 | "gpus": [ 18 | { 19 | "brand": "AMD", 20 | "name": "RX Vega-56", 21 | "min_ghz": 1.3477295700901486, 22 | "max_ghz": 1.707348476579961, 23 | "memory": { 24 | "value": "6", 25 | "unit": "GIGABYTE" 26 | } 27 | } 28 | ], 29 | "storages": [ 30 | { 31 | "driver": "SSD", 32 | "memory": { 33 | "value": "221", 34 | "unit": "GIGABYTE" 35 | } 36 | }, 37 | { 38 | "driver": "HDD", 39 | "memory": { 40 | "value": "6", 41 | "unit": "TERABYTE" 42 | } 43 | } 44 | ], 45 | "screen": { 46 | "size_inch": 16.203865, 47 | "resolution": { 48 | "width": 2158, 49 | "height": 1214 50 | }, 51 | "panel": "IPS", 52 | "multitouch": true 53 | }, 54 | "keyboard": { 55 | "layout": "QWERTY", 56 | "backlit": true 57 | }, 58 | "weight_kg": 1.8918947371347534, 59 | "price_usd": 2316.488298992809, 60 | "release_year": 2017, 61 | "updated_at": "2021-02-02T20:54:24.526251Z" 62 | } --------------------------------------------------------------------------------