[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci-test\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n\n    steps:\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v2\n      with:\n        go-version: ^1.15\n      id: go\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n\n    - name: Test\n      run: make test\n"
  },
  {
    "path": "Makefile",
    "content": "clean:\n\trm pb/*\n\trm swagger/*\n\ngen:\n\tprotoc --proto_path=proto proto/*.proto  --go_out=:pb --go-grpc_out=:pb --grpc-gateway_out=:pb --openapiv2_out=:swagger\n\nserver1:\n\tgo run cmd/server/main.go -port 50051\n\nserver2:\n\tgo run cmd/server/main.go -port 50052\n\nserver1-tls:\n\tgo run cmd/server/main.go -port 50051 -tls\n\nserver2-tls:\n\tgo run cmd/server/main.go -port 50052 -tls\n\nserver:\n\tgo run cmd/server/main.go -port 8080\n\nserver-tls:\n\tgo run cmd/server/main.go -port 8080 -tls\n\nrest:\n\tgo run cmd/server/main.go -port 8081 -type rest -endpoint 0.0.0.0:8080\n\nclient:\n\tgo run cmd/client/main.go -address 0.0.0.0:8080\n\nclient-tls:\n\tgo run cmd/client/main.go -address 0.0.0.0:8080 -tls\n\ntest:\n\tgo test -cover -race ./...\n\ncert:\n\tcd cert; ./gen.sh; cd ..\n\n.PHONY: clean gen server client test cert \n"
  },
  {
    "path": "README.md",
    "content": "# PC Book - Go\n\nThis 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).\n\n![The complete gRPC course](https://dev-to-uploads.s3.amazonaws.com/i/11r59di6zlyxf6g8o4s9.png)\n\n## The complete gRPC course\n\nIf 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!\n\nThis 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.\n\n### What you’ll learn\n\n- What gRPC is, how it works, why we should use it, and where it is suitable to.\n- The amazing HTTP/2 protocol that gRPC is built on.\n- Compare gRPC with REST.\n- Write and serialise protocol-buffer messages using Go + Java.\n- Define gRPC services with protocol-buffer and generate Go + Java codes.\n- Implement 4 types of gRPC using Go + Java: unary, server-streaming, client-streaming, bidirectional streaming.\n- Handle context deadline, gRPC errors and status codes.\n- Write production-grade application with interfaces and unit tests for gRPC services.\n- Use gRPC interceptors to authenticate & authorise users with JWT.\n- Secure gRPC connection with sever-side & mutual SSL/TLS.\n- Enable gRPC reflections for service discovery.\n\n### Are there any course requirements or prerequisites?\n\n- You only need to have basic programming skills in Go or Java.\n\n## The PC book application\n\nPC book is an application to manage and search laptop configurations. It provides 4 gRPC APIs:\n\n1. Create a new laptop: [unary gRPC](https://youtu.be/LOE_tkVFtb0)\n\n    This is a unary RPC API that allows client to create a new laptop with some specific configurations.\n\n    The input of the API is a laptop, and it returns the unique ID of the created laptop.\n\n    The laptop ID is a UUID, and can be set by the client, or randomly generated by the server if it's not provided.\n\n2. Search laptops with some filtering conditions: [server-streaming gRPC](https://youtu.be/SBPjEbZcgf8)\n\n    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.\n\n    The input of the API is the filtering conditions, and it returns a stream of laptops that satisfy the conditions.\n\n3. Upload a laptop image file in chunks: [client-streaming gRPC](https://youtu.be/i9H3BaRGLEc)\n\n   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.\n\n   The input of the API is a stream of request, which can either be:\n   - 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`.\n   - Or a binary data chunk of the image.\n\n   The total size of the image should not exceed 1 MB.\n\n   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.\n\n4. Rate multiple laptops and get back average rating for each of them: [bidirectional-streaming gRPC](https://youtu.be/hjTI35iKMyQ)\n\n    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.\n\n    The input of the API is a stream of requests, each with a laptop ID and a score.\n\n    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.\n\n## Setup development environment\n\n- Install `protoc`:\n\n```bash\nbrew install protobuf\n```\n\n- Install `protoc-gen-go` and `protoc-gen-go-grpc`\n\n```bash\ngo get google.golang.org/protobuf/cmd/protoc-gen-go\ngo get google.golang.org/grpc/cmd/protoc-gen-go-grpc\n```\n\n- Install `protoc-gen-grpc-gateway` and `protoc-gen-openapiv2`\n\n```bash\ngo get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway\ngo get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2\n```\n\n## How to run\n\n- Generate codes from proto files:\n\n```bash\nmake gen\n```\n\n- Clean up generated codes in `pb` and `swagger` folders:\n\n```bash\nmake clean\n```\n\n- Run unit tests:\n\n```bash\nmake test\n```\n\n- Run server and client:\n\n```bash\nmake server\nmake client\n```\n\n- Generate SSL/TLS certificates:\n\n```bash\nmake cert\n```\n\n## TECH SCHOOL - From noob to pro\n\nAt 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.\n\nNew 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. \n\nIf 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!\n"
  },
  {
    "path": "cert/ca-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFxjCCA64CCQDY35xYDFXtHjANBgkqhkiG9w0BAQsFADCBpDELMAkGA1UEBhMC\nRlIxEjAQBgNVBAgMCU9jY2l0YW5pZTERMA8GA1UEBwwIVG91bG91c2UxFDASBgNV\nBAoMC1RlY2ggU2Nob29sMRIwEAYDVQQLDAlFZHVjYXRpb24xGjAYBgNVBAMMESou\ndGVjaHNjaG9vbC5ndXJ1MSgwJgYJKoZIhvcNAQkBFhl0ZWNoc2Nob29sLmd1cnVA\nZ21haWwuY29tMB4XDTIwMDQxNTEwMjMwNVoXDTIxMDQxNTEwMjMwNVowgaQxCzAJ\nBgNVBAYTAkZSMRIwEAYDVQQIDAlPY2NpdGFuaWUxETAPBgNVBAcMCFRvdWxvdXNl\nMRQwEgYDVQQKDAtUZWNoIFNjaG9vbDESMBAGA1UECwwJRWR1Y2F0aW9uMRowGAYD\nVQQDDBEqLnRlY2hzY2hvb2wuZ3VydTEoMCYGCSqGSIb3DQEJARYZdGVjaHNjaG9v\nbC5ndXJ1QGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nAMlfsjdzwr9baKYRiL1VmbTSMxnCH7kQFYULl1073UqfkYrWSu3P8yiVSVu/EzUG\nwlN3C8wncRMn7JtBy3GuIjHnPC7t8E6ztcDWgear2RjzDQZmHVkcdAFji6hW2qEG\nLDIKUA2wfRXbjXnRqMj4ZZyIN6cHUJgo4+ZKwMus39WnwuWxQ/QNQhH432lfCHjj\nRiyA9AdJvObZVSUuHxfchbRR6aFFr8FL6/9hp5XTVfhz7iOEqWwXDwBQuBG8DHVt\nzp7i4BXx0DuTRhV31KAjz1nEdUFFZZB7rwcySAeL9OquObD8x1hehqkFU8F5z5JA\nO7nxWYOjTiRIVzLSwERi9TYAQbl8760jHlrn+0NDPAFXhsXmqYMYkNSJ486dNkT3\nyDr6kDeb4+r3bsN6g4GnXkXSRFcYUGMZA2k5tDJZbNd0DPMRV1QKy0Hz8R3JIJnN\n2bD+tQ0iS+iLpLO2kybQqnrU/1iJGju1Sg4VM5R9xOtMBlewpYOu+3gPimsDq+uP\njZg3GxQiBrfMBUJ35IlAHBw+yB/ObeEbcq3nZBQcetsVFIN3Qws6SlCxU3yFj+xk\n/gDOSJ0pPIuNlC5x9Rf9tV4LHZizE16hnf9Z4GzBW3Wu9hFw7ESqI11c++q1Lvq0\nb0Gou/TESjDlyoYE6ucKFz9I6ylqbjddCQFZl5u6AAF1AgMBAAEwDQYJKoZIhvcN\nAQELBQADggIBAKOD7Peqs5+BgBtX9EWk08rt29ALuO9lxxffcsAKSOYYCUOlwom4\nUz2+l8hZgrLG631iQR+l9KzpbIt4V8c+kX+NUONGG6evSxLYev77hv2Z9cdOAfWF\nsP4jgigcUsH6Zs6gEQ6PeBf/XqNnghu4i9dcmjdOiYj2hx7e9baBwvrqnC5foqcZ\nNxqWkXZSidtD1iECIEJSIJYLwMdPy5cHVdAqyiFSjSWFDMccqvUIMOjIqyRbxEch\n5UxGky7+3iS3r+UvTiloOkDDDOayYclc7CCBvknbLMnzWYcDoh+RiK4qQ/GWeEpr\nYq02Mg5h9C/phE6mZln3A0AgFwJ2mSzXJHy2zAbSbXybCzDmojcykq+44TxuFP6z\nSXGzo6nkQ6om2BF0Z9GNlPFkaqMrBIo/6kctZlG1sptswlr5BkowbZEwLUAcji7k\nkFzquWkZeOU1zQ1ZBDllajh6zWzJCnyR0AmRz8p7iI35XA1vHRYXsZlfVSjC3P5d\n0AiyoyyuInAzAtqPZUKSRAKUFkwyVvC48p4T/GxhgViNM/mn+OS905dt9DtBzTAw\nNkZ/SLYnRPpwGB7csw2EJgsTYRm5pg/QtA9pCmPib1IGT9SeLACJOMe+w0VAKuun\nI4pOaolfVC5dclS0rMxwC1S1wBp4YWDbnrxNa6lHRb+FDZBNwBGbfjry\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cert/ca-cert.srl",
    "content": "B141E873FD7B8575\n"
  },
  {
    "path": "cert/ca-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDJX7I3c8K/W2im\nEYi9VZm00jMZwh+5EBWFC5ddO91Kn5GK1krtz/MolUlbvxM1BsJTdwvMJ3ETJ+yb\nQctxriIx5zwu7fBOs7XA1oHmq9kY8w0GZh1ZHHQBY4uoVtqhBiwyClANsH0V2415\n0ajI+GWciDenB1CYKOPmSsDLrN/Vp8LlsUP0DUIR+N9pXwh440YsgPQHSbzm2VUl\nLh8X3IW0UemhRa/BS+v/YaeV01X4c+4jhKlsFw8AULgRvAx1bc6e4uAV8dA7k0YV\nd9SgI89ZxHVBRWWQe68HMkgHi/Tqrjmw/MdYXoapBVPBec+SQDu58VmDo04kSFcy\n0sBEYvU2AEG5fO+tIx5a5/tDQzwBV4bF5qmDGJDUiePOnTZE98g6+pA3m+Pq927D\neoOBp15F0kRXGFBjGQNpObQyWWzXdAzzEVdUCstB8/EdySCZzdmw/rUNIkvoi6Sz\ntpMm0Kp61P9YiRo7tUoOFTOUfcTrTAZXsKWDrvt4D4prA6vrj42YNxsUIga3zAVC\nd+SJQBwcPsgfzm3hG3Kt52QUHHrbFRSDd0MLOkpQsVN8hY/sZP4AzkidKTyLjZQu\ncfUX/bVeCx2YsxNeoZ3/WeBswVt1rvYRcOxEqiNdXPvqtS76tG9BqLv0xEow5cqG\nBOrnChc/SOspam43XQkBWZebugABdQIDAQABAoICAQCVxKyhfWEsPOnaCVRvrIiC\n6YrD75L0arf2maZb2zg8Ve1DGxnjQTQRzOYgbD32xC4nMXT+w57fpmPdHNQYmnAo\nOViTdrexcQsOfvth+hGe8rWPOsc9DWJh3g1yiBZWiGa6WN0tMUP2y7GvFnW38rZv\n8wehHFmesVq+Xn6BfPOEzh6wAmUN0AaBo11V2y5L6oy4cLgN65OpBZ7D5keN0Z9H\ne1yNa2zKEJNW/uRLFEDuZhqJJBN1prirfV1JI1kIxUBU/1u2NoCurlwDf3oOGFQQ\n6YJjpx9gk/ybF5RmuHrRR/70WSxR1wvEDYg7b0Mn/MnvA0eWFhD5/yuLSx9gPVEt\nI5BIF/neUsEv5g9/T5H5hgLV5vs1wBOmeliGNBVT/harXtiztDgpd+Kja2f0B/+c\nQkliGo3Iazxxo48cpKt+F3nVKxUtcMTKUgBFGWaS+uKR2dMA908oxh9vjL2aOEF7\nYhtaXkl7vIAMouVWK0WXx5Mc6ks3gurUFMflmLslPvRmqAURYqz/kZNRqxZE0OZ2\nrI/mVzck0zhp4PgKJIbRchL9Y8CgasgCPs2EF1y74Sw+cmN0PL9mk4keRDL+JnbZ\n2a+57zHMqd3n1/w/lSy5BQZ0qvyJpc6wXlU+qKTbNp1FzODBcEE9Rj2j4i7yElvK\nxPzkVnOWfite2Tf8+XOMwQKCAQEA90tOIaZNLyN/xJLS1au7u9q84zfjEOMvpqPK\nBniPRshR27EKX11gOCi2F5qcdEgvV4TOdg/n++Xck7MZeXcrSvcCoiCDe0JjMC34\nFW/o8376fma1mDQ7mlAIoYB22XfZidqany25pTxOJKZqEcsFYmUrJGUNZHoqjp3K\nvnD8Xl7JYLTZBVDU8m//GSO2Zco1lp/wttzF/vg4I6nW8uj1v8Ery6YC32Q7U9cz\nVhqeKnHOfXUOmb0YG9PCljudK1mJJ74ldpJ0uhIcKdMQQi5ccWu1kYgOu4JsAbCO\nWlCS6bptG82ysv3iqr+Mg7vnckbGk0UMl3w4g4M29ua9KOgMuQKCAQEA0HaKvfbP\nPiHa4TEt7iPJ010mAZmJnhpUPFz5sVRyJDJa5SPMXx3rwQb8+nq6sw/YzzGr2DEm\naYz0Csn66EHgilBUQyr2jhLGdtC2iWmDA9vxew9VRNOYyerkTwpEtQMGycIitv1F\n1MomEWCh56T3JN58JwgjnG/O0D4IRSRsfJsBERlHlQObJ7hEdd+cuoRIJEsrQzxg\ny3Z5HmFDAqLO0WOwoJV1SaNSxNwEa3GiEFydNuxgDpdUrCAl3y8wYXQQNqyQaZQw\nlu78hk1rMf/jGkUS/y59CKrDbS9qBYjT55yS3czfsIodg7TsP8ePtHszyW4jh3jt\nup6mHHG/hN/UnQKCAQEA15/Z6I1RD6EsbwJ2w8iSUSJRQO4iFz+A9RQru6izhUx1\n09Fy8eRBWbZlz/8IHHw0i2NJgrgr1wB+bGrl83ttTFhE+4jOHFOumPv8LPT/chFt\n6Xk1LhmdPtg6LlgNSWGvVPw/hjwge2sx19Mi+ZDEiR8dlwZlvw6mvOPpPFTYOJD9\nlk0aTgBFLX9qN0lkaSz/vO6IvWmTWtakXLRisDtgzGpq/Y8rQg3rjRc/s/xRnUDO\ng6XlrTesJddm/AfO61WOuhCaKeFZ1kSkOfPHYW17PYplLxgrgGQgOPjxpt5Ku3HK\nYUviXR/y3F9Y7iSRkpsT2qWCbTPrMwDelDptBZYQQQKCAQAbXWRLGYoM8u7DnuwT\nlbkZuOGTVi9dhMFIB0Bzyc0N+Vo7OB7M4aWf+iXdT50QgmUIldGkGJedRXaHsAny\n0SsDnRXil1I0RjytPiqoESS0rfueFt4vocMtxlrgEU4BoPsUIxrhgI/ZJgwnPdMj\nEGGtAlOz3/qkv3ybk3kMcoKXPNXAA9yEsCt+5E6AVrFBProloYR1WAiwzMWLemMM\nhoi1retyuQvjdcAYvXULEaifkzjEC/V0FON1kObHzG8Ca/Tw6Ggwo9ZZCdg2XRVU\nQ+3w9d5Phy+8ooXy4EV/on2GquQQn6NBjm/faTGWEcFIhN/AcmfRkctLMyZFF513\neNZ1AoIBADG8nW3c3sIWSIokw6NdI9TLMeQVqVSW2LC8K6L7ZDjl3Ypiu4fT1hBt\nCvF3hdvAed+n8Hgb8+rclmubbIdhAVPYKvsuyMGP7fhb7vw1HOsfwSm8ovx+AGXK\nsiMDXDZKAvOAUcCnO5kPosTwfl3HJhbLArzl9AcU8wvSUphbL8NiJ2pdujs3863E\nE9NqBbjd9AHak0EZ29CEI0MLX6/9ndjROjm4Hd1FpoQexm4mh+JnPhC++8aD65qA\npOnVnS+TOw3nGP6KHyorYEipzPjg+jFzsgQhup01Eqv8jZ++msHrgnj507Tf73Cp\nuwawX4qaay9IGjuXdE8Py3mM3xpDXPA=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "cert/client-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF4jCCA8qgAwIBAgIJALFB6HP9e4V1MA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\nVQQGEwJGUjESMBAGA1UECAwJT2NjaXRhbmllMREwDwYDVQQHDAhUb3Vsb3VzZTEU\nMBIGA1UECgwLVGVjaCBTY2hvb2wxEjAQBgNVBAsMCUVkdWNhdGlvbjEaMBgGA1UE\nAwwRKi50ZWNoc2Nob29sLmd1cnUxKDAmBgkqhkiG9w0BCQEWGXRlY2hzY2hvb2wu\nZ3VydUBnbWFpbC5jb20wHhcNMjAwNDE1MTAyMzA4WhcNMjAwNjE0MTAyMzA4WjCB\nljELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkFsc2FjZTETMBEGA1UEBwwKU3RyYXNi\nb3VyZzESMBAGA1UECgwJUEMgQ2xpZW50MREwDwYDVQQLDAhDb21wdXRlcjEXMBUG\nA1UEAwwOKi5wY2NsaWVudC5jb20xITAfBgkqhkiG9w0BCQEWEnBjY2xpZW50QGdt\nYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAN1B/FRa+rT/\nAQH6s5ON5GF3SnwyCv1ALnzwWb6AXFBKI8vx38JheqX8igqWyjOMzITO86YNQFhu\nIx18XsRRCOPG3lcCeR6n8/uu2VyEPUcNfRHGhs+JdmT3jZeokafwsCQfwK5A2ifY\nqU3klElTwwVwD9HgTQOnqOgXEwzpC01EzIvQtR6/49ANOJUURfO8ae2TglXv90yT\nBI8u4/lV2dKPWjmxsnUpadKhKSHGU5eoT3Dyg0KTig1pXYEI6aW+qBXW0B/loone\nfnN2W/TvJ9u0E9ZOWEr8Ekpz7Cg4psZuGTf6PMvjEELCcm9pT/jAjOOqmKeZYU7m\nzecsuzmEi4d3X5XsnDXd2LuZ3VFiW9sdp0RZDgWTF7J1MJ9kg7Lhc46zhtrLTqlB\nFisSVMWGLvhpqjVTBUIWCuAeKxVkZgStVitzrkxk4Av5dYI5XJqDCswmEIWnPKSX\nt4B8S0F1sYCWYd4+GD9zkCgxPSvCNsGMKraR4E3M+EcBREJUezxAvJN5yPur+08+\nr0hsQC7p4LMTZlVLsBOACGALCfQkJhM1ycBNBC1AQsRkqnsVuydlqYTef8pSCDUp\nqGoLkokoC2BfAVEzuHRS8PoNEX60lwU+pprCmGtbKQuFwQUX3FzyMWhtA9OSWrfD\nzShb4C3slKu2pk/pw6J2fAdkxAe2wBBpAgMBAAGjIzAhMB8GA1UdEQQYMBaCDiou\ncGNjbGllbnQuY29thwQAAAAAMA0GCSqGSIb3DQEBBQUAA4ICAQBsxrzqLj6WWqrn\nQFr6fEsOiprIilwW1/de8ATnubbkItjJmRsdIrZHVDgnahVr+gPnggwS8R99h4gr\njalG11Fa3narO0CXldG2MGrnkU8AKuSu6dmwCBmKmZH5IhajcbhdRDuR3JOQBAn7\nYj4LZylG652Gq48hFY3bmM4/P4bKi+z7ZkoUq6iitt57Y66blfXsKUNwjtFxJEzv\nfigvXKq1ZetAuVNcmOROvYaFisyw5Yvk7XXBw/7DwCUScKBV71MOS6FrkQ/IQ/Xq\nqrwD7qBacozUZ37sWULihyjJNuikDcUAVg74jcFSgohluSpbigxgXeDdWgwh8Q3Q\nn3ts/ZOb0XDeUp4QAqOxUShUvVEsJGi8NCOWjpp/enzeaDJ85QgCVvK8McIxQgbu\ni2IXmnHHzgc6PMpE79qOffeugtUCN/6odvveFq0rN3QJMKFU5mFjitWFfko2OVLv\n/jMuJ00cISCGJFXIDij/XzetvwD0hGME9jUhp68xdfZDX1Tnc60gqSyJqaFg6iKa\n0iSvy3ShR1k/VATsDP1DTQlon39dbOYPhsSkk1Lc4RSMJBgJ6lFum+9vd9/gVZEG\nJno5YIiGZphbVjE5/KQ3jtFL5WZOZHPk+FYMGxwey5h5nXN2veWhVCkwx9K4YaCB\n9BhkA0M0G3zQoGqSv94DTeI32qaxNg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cert/client-ext.cnf",
    "content": "subjectAltName=DNS:*.pcclient.com,IP:0.0.0.0\n"
  },
  {
    "path": "cert/client-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDdQfxUWvq0/wEB\n+rOTjeRhd0p8Mgr9QC588Fm+gFxQSiPL8d/CYXql/IoKlsozjMyEzvOmDUBYbiMd\nfF7EUQjjxt5XAnkep/P7rtlchD1HDX0RxobPiXZk942XqJGn8LAkH8CuQNon2KlN\n5JRJU8MFcA/R4E0Dp6joFxMM6QtNRMyL0LUev+PQDTiVFEXzvGntk4JV7/dMkwSP\nLuP5VdnSj1o5sbJ1KWnSoSkhxlOXqE9w8oNCk4oNaV2BCOmlvqgV1tAf5aKJ3n5z\ndlv07yfbtBPWTlhK/BJKc+woOKbGbhk3+jzL4xBCwnJvaU/4wIzjqpinmWFO5s3n\nLLs5hIuHd1+V7Jw13di7md1RYlvbHadEWQ4FkxeydTCfZIOy4XOOs4bay06pQRYr\nElTFhi74aao1UwVCFgrgHisVZGYErVYrc65MZOAL+XWCOVyagwrMJhCFpzykl7eA\nfEtBdbGAlmHePhg/c5AoMT0rwjbBjCq2keBNzPhHAURCVHs8QLyTecj7q/tPPq9I\nbEAu6eCzE2ZVS7ATgAhgCwn0JCYTNcnATQQtQELEZKp7FbsnZamE3n/KUgg1Kahq\nC5KJKAtgXwFRM7h0UvD6DRF+tJcFPqaawphrWykLhcEFF9xc8jFobQPTklq3w80o\nW+At7JSrtqZP6cOidnwHZMQHtsAQaQIDAQABAoICAH2jIINN/hqUyp+zGhFpewuV\nT2hiijbwIPW1DWDNRp4Y22bNe7/G1nw2gLQul7bZ9rBbS6M41xbfw3TU0IMteJzO\nqiZCM0CjIjoCOU79kEYudJyJXLewWNhQcchyYfM5CuwYU7MfBEGoF8sxRrq0o4MM\n9Q66DUFMDO9tWtXz5wUDUhr6cj55vATB3SVaE7apgIT1RAdEceq7eNVNTQqiI0Qb\nPqKQMsOwtnRyKwcQtRri6ek67Cn72WJwODYzN2l0b8Gm7xuNq9QZ0TgDN4hH3Rw2\njyUb66r4o/I/DRRxxtHaZtuQbsFfuDYQcCavaEfaHqaQkopo4AaLrNPeZJnul8NP\nwhnKwydsrF40bajjIgQ7hFas/fy5toQPI501gTIGkXAWg8T2+qmFuxGqIAwGajBt\n3bPJ+jbo+c3VZg4xS7yTLaa1EJcVKR60wkGxiJH0qDI0ly8WgUlxe1S+Q4WrloaK\ny4kchI83zKH2HplFF2YdeUoP2taxWpZAfWOoC0YgTR5BmxVoK6lVpd/zZ3QWP0rh\n0Lx4XTtlVphgYC3EV3ue5dmFo7Q0hnYIgbdRyW9J4ODmszcrRwqwyTx/v4oTmHb0\nFKrsjSpVkvxB3ZMZP5En5bKzsTMneu0IaLhQrXNeUEVwTH9vDSnXX0ugCdDL7vKV\nGaFcl9Ns8i6hW9X2Kk3ZAoIBAQD9oAmjo8mA8jStVAo3PvzpR2TjQFg/m5QqhmhB\nju1ZJhBlcW+NRUBKv3FxgtdsRp+eSLVtc5BeryMSHvN1ertXfmfUfdihMrKmJ9Gn\nMjxbyqXnBREe8ALrNNjYNgXzviBNunxNaNjS7F/niAvHG93S7Bl3jy7nOy4VyxPs\n7CkkcaYIJ3octTMkC+t00MPba6Ya4doUmpmgPWtiX3rHktn1xYVmXueWs6/Fyvfk\ncAXsMxnjsFhw3LneILiYBW32d4+p4L7X3vhL4j7mbMYXf5XqwBOg3eWPVODpqVGU\niGFSOAC4CUtvrGIzFQ0tCbjA9w8OXdd6xu4JzdxAyge0JAFXAoIBAQDfVFxGykNW\nQbX1PwP+v/n/3nmgSM07zF8xFy2tSu3YhF7eIje56zGDhaGVlN6wU0Gqm5bgENeL\nFFBSLIzBw9Qi2mgRxd2tXB79v3swukhhGmCFVwSOWbPGQrDuOXHU1EEHmq8xRMo4\nmc8s9nCJotOe1TN5BcbUVuPRu+9zW2lEhuxoMn9ZhtiHOUop+TJqb3wMICr4K137\ndfICtb25DK6M5r5Es9DP1G6e2OjiH40Kkd+/vsxZu2gzIxyGcOcqde20yS+ihm76\nyjPKkwV5s2HLFRv1hiOc0PUNqzLWC1pMEKiEqPmBtIh3mj5vq6ZmL7UXedT4V2my\ntm0n5PtqOKQ/AoIBAQCY/any4UETHIe0KqbC7qcHXT65ar4RGJtHD67iJQJ9rV1k\npAnDcQu4S0V2UJP8R5nPlFKExJpI02LXcn4v1qodvC2L26IKkxd67TgloEMSp+pt\nsfvC6ssH8OgBfI0YnA7GdIC4/U8V5OpxMvrPz7p+mlc+bMvBRkylbswFNewXhMq/\nznh1ysQfsWUGIUyUFpqrSqQPm7aiF4qoW6onqyj5fX3b49HVcWzNZoMkdILOGYE7\nfMvMwQkJujk/0r6jVzn9Iopck665r697tg/Eav0XD2iHuHLahDvsF2wTqjTysL+W\nRF0R7y2JXOCG0390P1QAuZDbChbbKSf8mSIOg02fAoIBAQDAo9ExEvmApw/gq2mz\nzj9EsdAyLXozEbgu7TJuX8rIUG5QqC1vhuvf6l4WXCK28CodkzZSstRqWKxsJZeI\n8HXFVqYcZpQwHN1yvj/yKU2TzR/jBMueSswiwZZC93Q0RJ6Pg6OJGTBiIHKv8yfh\n4X0vbfKHey8mLIk5eiYzWG92N/gmbSCixglyoz1Q9W7ClsXm47yM80OPTA7kvYYY\n4FKUodkQBBejnjeJd8tyegq8SlY53MgCwwA/1BKf+TW9z5mqrzwSsml6lP6Vx7oa\nX1yExAGpCPshIrGvB7TDI2nRYTErtWH7uxFYMcmXo/XWAWLxDBtj2GsJSAjiN8eS\nuacbAoIBAFTcrGjSRo/v8DT+r8z5BClwuBmnsRWzTNx3ay/EMnLdGv2Jcg5I+p7l\nVjioNmz/mF7Ff94nH0VohDrsbmKlVWs8zOxiotG/UUhgHfp2MX1OTxXzxwbxx+sB\nEkb7WuZ8yxWI7WboH5r4ENla3SeQb1HETNDhJoqVCi67XGkq5rY4DlNXtZQZdkzB\nSH0c0JVykXk265qsvpthKObTakb92azMCdToipl0bdwRWwzjLlNV1cqJHdtaD40Z\n0u8CfN8S2w7nwGIcLLWQVbN1nx7nspFmbo2kezu62YHpGh+hCvwGWTLelK6lAQWS\novQGWC8qf6sde8JLYDVa3WC36B1OwTQ=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "cert/client-req.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIE3DCCAsQCAQAwgZYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZBbHNhY2UxEzAR\nBgNVBAcMClN0cmFzYm91cmcxEjAQBgNVBAoMCVBDIENsaWVudDERMA8GA1UECwwI\nQ29tcHV0ZXIxFzAVBgNVBAMMDioucGNjbGllbnQuY29tMSEwHwYJKoZIhvcNAQkB\nFhJwY2NsaWVudEBnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\nAoICAQDdQfxUWvq0/wEB+rOTjeRhd0p8Mgr9QC588Fm+gFxQSiPL8d/CYXql/IoK\nlsozjMyEzvOmDUBYbiMdfF7EUQjjxt5XAnkep/P7rtlchD1HDX0RxobPiXZk942X\nqJGn8LAkH8CuQNon2KlN5JRJU8MFcA/R4E0Dp6joFxMM6QtNRMyL0LUev+PQDTiV\nFEXzvGntk4JV7/dMkwSPLuP5VdnSj1o5sbJ1KWnSoSkhxlOXqE9w8oNCk4oNaV2B\nCOmlvqgV1tAf5aKJ3n5zdlv07yfbtBPWTlhK/BJKc+woOKbGbhk3+jzL4xBCwnJv\naU/4wIzjqpinmWFO5s3nLLs5hIuHd1+V7Jw13di7md1RYlvbHadEWQ4FkxeydTCf\nZIOy4XOOs4bay06pQRYrElTFhi74aao1UwVCFgrgHisVZGYErVYrc65MZOAL+XWC\nOVyagwrMJhCFpzykl7eAfEtBdbGAlmHePhg/c5AoMT0rwjbBjCq2keBNzPhHAURC\nVHs8QLyTecj7q/tPPq9IbEAu6eCzE2ZVS7ATgAhgCwn0JCYTNcnATQQtQELEZKp7\nFbsnZamE3n/KUgg1KahqC5KJKAtgXwFRM7h0UvD6DRF+tJcFPqaawphrWykLhcEF\nF9xc8jFobQPTklq3w80oW+At7JSrtqZP6cOidnwHZMQHtsAQaQIDAQABoAAwDQYJ\nKoZIhvcNAQELBQADggIBAHUPxGmovTlpVp6d9tOv2RbrqOq/9XDrul6O21/wsU0A\nZWlDyIHCrxjlTg8YrrHl1tiJYpi03pb707j6pjX6Q/AWLrTHuLo8l2cDnDu+xL08\nSWZ20DtmM5GnYdmJzjY6mR6b5lYDrGlqFaynKCBn2IgB0AJeraGSsh/+cnhOgzya\n6nKm8bOM9DSkcuki5b9JTYjtnmSUlwaaup0rlkKtnt0XLV4jKcQL4DurYfSXUXgs\nsazbKj/N2xkRiSyk40lf5ASPFAyXDOr57c+IMKokhcWZvZunZc+reqWXkwvJQwZ9\n8UNsrHmNopDbQI1mLk4Ky8cPRWbtdyjnJlwPc59TbGmdVNWm6KMmCwXqiegORivo\nfwhOPEXvTusrcwgyREtS+bYOG0sFVGce6nxcIjBf9sY+igXGUTm9UyQVGO0YDLpJ\nhJLL5bb9iv4nurokFWuoGI0H3XpeiLXBO6oPA/PXSEru6deP9/HwtGADVcoxQFa0\nfLn9bA7lyyIXm6UpQXIAMv/Ptqv7v0SoesrwpxloJQKa43Iv0CkaFrWfLedCdayR\nBgqOMvFcN6pzfCnaEpFb/CppBPP7Q/wVhAuIlmVq7M75q6/rW819RLxZFaNF0Yyx\n0P5n1b8sSlUuQPPG0JNHGDZbM8Jz7iKMhcJuH8CQGI3EqPJVM3onvlYV1AWDKWI3\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "cert/gen.sh",
    "content": "rm *.pem\n\n# 1. Generate CA's private key and self-signed certificate\nopenssl 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\"\n\necho \"CA's self-signed certificate\"\nopenssl x509 -in ca-cert.pem -noout -text\n\n# 2. Generate web server's private key and certificate signing request (CSR)\nopenssl 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\"\n\n# 3. Use CA's private key to sign web server's CSR and get back the signed certificate\nopenssl 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\n\necho \"Server's signed certificate\"\nopenssl x509 -in server-cert.pem -noout -text\n\n# 4. Generate client's private key and certificate signing request (CSR)\nopenssl 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\"\n\n# 5. Use CA's private key to sign client's CSR and get back the signed certificate\nopenssl 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\n\necho \"Client's signed certificate\"\nopenssl x509 -in client-cert.pem -noout -text\n"
  },
  {
    "path": "cert/server-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF6jCCA9KgAwIBAgIJALFB6HP9e4V0MA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\nVQQGEwJGUjESMBAGA1UECAwJT2NjaXRhbmllMREwDwYDVQQHDAhUb3Vsb3VzZTEU\nMBIGA1UECgwLVGVjaCBTY2hvb2wxEjAQBgNVBAsMCUVkdWNhdGlvbjEaMBgGA1UE\nAwwRKi50ZWNoc2Nob29sLmd1cnUxKDAmBgkqhkiG9w0BCQEWGXRlY2hzY2hvb2wu\nZ3VydUBnbWFpbC5jb20wHhcNMjAwNDE1MTAyMzA3WhcNMjAwNjE0MTAyMzA3WjCB\nkjELMAkGA1UEBhMCRlIxFjAUBgNVBAgMDUlsZSBkZSBGcmFuY2UxDjAMBgNVBAcM\nBVBhcmlzMRAwDgYDVQQKDAdQQyBCb29rMREwDwYDVQQLDAhDb21wdXRlcjEVMBMG\nA1UEAwwMKi5wY2Jvb2suY29tMR8wHQYJKoZIhvcNAQkBFhBwY2Jvb2tAZ21haWwu\nY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwOsO3r/3ul2O2W1k\nka9j7VYLmXcBOY3Lkoo+k4N6dwLXH9NADO5cBQsLFyljHZ+gwbeV0pRDTs/PFwNA\novHToNA6I2Uw9TI3Yz/MzUlZ4PpWkhdpYEnXWOgccXeKTFyYX4oKCGiTYLyDV7DH\nE9kdMPX+sz7aiayloUKzOEVdLFtUIH2V8e9U5j7zKWAGoPCqbWYRGoAG6y8i3udi\n6pS/7ETq0MVI+QXcQ8+eOrzNAZjkagMhEKB1czLFh7bb3YtHz/fl/Sujqn21oKWI\nY8CAHuMxYFMZO6im0QkztiL35vRPyozhi+Kr2LIaN4mfCKBjps13uGvYS8fDZbDD\n+F8ix/Avhbz4PWEm2S5QdDSz44/69sbFKLk3qqoz0tO4byu5ADjIsnba9/9ZeqNx\n3IiIXkPKMfNqfgq1Ti3M90aWW2uPF96AXWc/MKKHErA/AqgOn8XTBjzi/VBenoHH\nNrsNoz0j5wV7i5SBPAGEOu5s7jV7hc5B5geWIpLOklUzryGiv5IRsM3U/dV9qYry\n5GPywW0usxyyUKvTCe/GLt+63h+0xdGTbPs4Rd0twX0V1ZOpzMjnpaYtmczl+x8S\nR0ZsTzyLhXsCzUPY5yNuGBNKIXBtwedZCEIZ3vT23lzvEgagUhqkymocVi9lXL+Z\nU8I6Ketb4FHIjVtvCCZj8FVvHE0CAwEAAaMvMC0wKwYDVR0RBCQwIoIMKi5wY2Jv\nb2suY29tggwqLnBjYm9vay5vcmeHBAAAAAAwDQYJKoZIhvcNAQEFBQADggIBABmv\nCe/pzmsilW9rFKPnZyr4zjdevi3zK4hn78iT2eCMoH48UigoFERphtqtnoinmHQD\nqMFRfJ7ZBx6wPYo/fgxp+sXjMKT+hWUtU2CNQoVbeH2dpKU7xeyh82Q7fesajIYi\nUrpuBlYBjZywNPJrpSKJyaq4H37Yt60SCA8GUrXb5LHcurRSJNtIQ8muOVgwvjAY\n1mzADfOW2wlVX84oyXBHFo+3iXvjKhp8AahTCiQak4MvJwMPuiUJf8qIaqNi1w0s\nOt5p4QtLECYrfJxtsmyGRCOrCWdXpj2oPY85bz0CMdf7IuufFoTarhBm1E71YhOl\nSgrnj3w89HxIYDG/DvaybXO4/bs62oyQzcLzgeyoJeRLp23LpSR+8o4OV9koKUyQ\nDBMNikKIqk+pZWEiJ6uILYUH1Ay8BBkUt/XY5Eeuy5RPtBrPJENHNAwgN5HU+h5i\nglOc/rCpgYWWIVeHeickMxr3lZXVel9viNAucmLu9OuKi/jHksJcqimMDAwZGlnj\nwaeLHv1Ea/DX24lUkuA2m5w9RCcNI2ZmTmL7svwZrkdNmLLkcqz2nWBFyNZC5+P3\npN4Qe/thzJWsss1JB/Kn6V/2NX2/yr6eKrjt8jo5rucUbdtysOYrjCMBm0RiCJbK\n3I3Nv1KsekkLnFiQIW7uXwLl5FJcaXHLfvYZlusV\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cert/server-ext.cnf",
    "content": "subjectAltName=DNS:*.pcbook.com,DNS:*.pcbook.org,IP:0.0.0.0\n"
  },
  {
    "path": "cert/server-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDA6w7ev/e6XY7Z\nbWSRr2PtVguZdwE5jcuSij6Tg3p3Atcf00AM7lwFCwsXKWMdn6DBt5XSlENOz88X\nA0Ci8dOg0DojZTD1MjdjP8zNSVng+laSF2lgSddY6Bxxd4pMXJhfigoIaJNgvINX\nsMcT2R0w9f6zPtqJrKWhQrM4RV0sW1QgfZXx71TmPvMpYAag8KptZhEagAbrLyLe\n52LqlL/sROrQxUj5BdxDz546vM0BmORqAyEQoHVzMsWHttvdi0fP9+X9K6OqfbWg\npYhjwIAe4zFgUxk7qKbRCTO2Ivfm9E/KjOGL4qvYsho3iZ8IoGOmzXe4a9hLx8Nl\nsMP4XyLH8C+FvPg9YSbZLlB0NLPjj/r2xsUouTeqqjPS07hvK7kAOMiydtr3/1l6\no3HciIheQ8ox82p+CrVOLcz3RpZba48X3oBdZz8woocSsD8CqA6fxdMGPOL9UF6e\ngcc2uw2jPSPnBXuLlIE8AYQ67mzuNXuFzkHmB5Yiks6SVTOvIaK/khGwzdT91X2p\nivLkY/LBbS6zHLJQq9MJ78Yu37reH7TF0ZNs+zhF3S3BfRXVk6nMyOelpi2ZzOX7\nHxJHRmxPPIuFewLNQ9jnI24YE0ohcG3B51kIQhne9PbeXO8SBqBSGqTKahxWL2Vc\nv5lTwjop61vgUciNW28IJmPwVW8cTQIDAQABAoICAQCQuYZdSuxAbmF08aEJvecc\nLHnlNibAE4TNuVI6fd8ImyPhpywcx1BXJDK8vHqzxYXm7Z/C6yEXZcR5AiKiWwKl\nWLDUztwMhhCRL1KoCsgXhBYf4NpXtu2LsA1uffxNTwWsXrUqG7G8V4+84Exosm84\nxMK/m3582/0hXhVvOHIujZEuEqjDaAVr+XuX5Ybzg7iG+5QHKlaGZsUlLbbCPrdA\nO29hES+uSVw1rvKIJA0zjoyEjzZl78pMkqEnL+H/cLZ96P4rkGpmw4nXK3eHRemX\nwl7PYWfDnsEOfnXBxLfvFgcp78hglbrPhMUwNtkMsq4ve2K+AoGwT/thNVu+3zgK\nMj/L+QSOKW7uY+E5oaETy7lg5P+65++4/iGyuszp2zX/iPMJcYwDoYcIwmIYkyIZ\nnM5RNNCFoPAShKmkoboRh9KmVnzvoYPIpvYnNKGmQ/UCFxRm+l7dF9hrY+ZEYGcn\nbF1nhKW8/i/GsVVvWOSN1NUdqZG80DwXNBruiKI171xPDoeEF+tlBaxz4Vk8F+yc\n01gwAZnMMdF/3DPODmEEE40WFzSRy/HgzSQoLFFRpMU945S+LrR/1nzOFHgHubgz\nstlp/oeV5IXZXbe3hnfWmfGSgEw7WEAlEvsofna/6REigNrBWsaZzKTlGlYuc1JK\nZgHoPjrwrbZgkYWH5fQfIQKCAQEA65PIIW4RhuvaWXPtuojNm0RHN4W0tvrkfhrf\nKwnUM7atCqLHpf7doBJzpGSo3Q8vcaAhkvmINKVXogWV/mKulFE3QepcTjEpQNiQ\nSTrRm/Qg/Jv9zmpDYddtuVm3sHqfmONmsIBuMv/hPGxbuKr2x4lQZPI68wth0/0b\ngxkNVF3JAksTyDpe7Zr0QEAKFiBwptTDnnZ5YdtiN3E1IwWle2FIa0AD8GAk/LH4\nl/1aavgh9imEzbGqvlu8qU86LrPNfVjRUMe1Hk9Mpe8D9BuL6kk/Gx7b4bl+ObLG\nwiIH+AVJa8RE3k7joqgmrVDGfL8q3SFyiNV/msyo0yLSY4bORQKCAQEA0aSIt14/\n642yzO2TTtO9+EThG2PMV14sAWWWp6XRWtPUACmaCuI7Daz/taeymT2EiHx6x7pU\nRw4LZrhXPbjI0X4GuH0VSxYWnP1rgNqiqkTBMe9JNmNmweGHr3Ok7HMDX5QMKhxX\nnFRYpRczrkSa87E5Js+jPRwiUUizOpfl5TsEAxS1kgcg+FJLqh2SfSOA8dBPArch\n4W3O0L/E1AuLhfVUQLkTfUjQQJVVaZ2E3pWBLyGya10UbAcOlfi5MxFrK1M7gSRv\nyMrk4l7N+68rZEk9DR93/p0C3B7kKB8lVQDfsaqnlMo7VXwG0oHTAsExV8JgPwQm\nDMkkU2JPI0OaaQKCAQBkc0JuBsoQdvdHF2iyFm1dnJKleSzirT7LCthIOMu0NVu5\n4kkxXejQva1z1rwubrAzSi2mxyIuGKayXqFjtF5uvebLA4zShqHpla6Imz0Pu9xo\n+ncSEjujN8IAu+HYraDqB2UdM9ZJhtRa+HVv2+6YjNOsB6HdSugvBYk6sG7/n3H7\nuVm5EjKyLFWkI+ppHvIKIUU8h5YghPRvYaVfxqOWZZgEq2pCkCyVV6oB3TU10ZJh\nrbiEIRMGUoWyyCauDVs87KdsQ4vWXcf4JV/RMgHKJ+txvAnUSU0qezHHS82ME20I\nN8uJ46erDvpXAs9wF+/GFOIKuMbNkiEWzo1ZhPzlAoIBAEL8rQbSoeAVlfVvUGuW\nsxP6hmdnGysrlyoXGO2WyW+ZUht/L46cvTvgdJDJ9gKLKqcmB2F8g2N09GWtL4s0\nWU6/U0xuA3jLpQwi2dABjIqVj5nyGNW9K192PhHtBNzc304SE1T9W21DclPGNyhP\nGagWj+l73XAwZjLM5SAq2zXFBsIpQt9XUcynFzBTZLSBvLkH08dNVxEeMkB3lmAf\nFEUIoBRSTwzwUELitLkbsRIieXXi8Yzm4BiopJt9L0hHH5RncxMP3nwtgLdoja8H\nSPkxgcWIsaH0764AXO0JDre7oL63hfbAK/djuxZWj2NI8ghVvsVEARiCyQ2v0xO0\njUkCggEBALfx/VF1oYXVdpdHLfFZ0A7qgrVOpFa+mwzoSUx20/x9KIPsaKU0Rpc4\nrRcdyGOqOm8ScQacpxkXqyh/akhG7WQysxp0EGof5IReWJJTTdlzZO0L5FN/EIrH\nmLEojmA5xs54De6ywWPxmOsnk2jxYaGfUzVMzvBaJSkTLfIr/JGIsZMP590L4MCC\nq10rQqmt+I5HZlsdDW908ThT1VwglRW6kQskxaEIsY1vOqi8q9AvrVNjSlga8Jp3\njrNe+80SA3nglFU16x7t7BkcrjTeE8zeuwRzzt98xHJSto6QRS2PWwCwGeGnyO6s\nmQ46mZhKvL1F0M1v7dIRGpBcXSwHnIQ=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "cert/server-req.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIE2DCCAsACAQAwgZIxCzAJBgNVBAYTAkZSMRYwFAYDVQQIDA1JbGUgZGUgRnJh\nbmNlMQ4wDAYDVQQHDAVQYXJpczEQMA4GA1UECgwHUEMgQm9vazERMA8GA1UECwwI\nQ29tcHV0ZXIxFTATBgNVBAMMDCoucGNib29rLmNvbTEfMB0GCSqGSIb3DQEJARYQ\ncGNib29rQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\nAMDrDt6/97pdjtltZJGvY+1WC5l3ATmNy5KKPpODencC1x/TQAzuXAULCxcpYx2f\noMG3ldKUQ07PzxcDQKLx06DQOiNlMPUyN2M/zM1JWeD6VpIXaWBJ11joHHF3ikxc\nmF+KCghok2C8g1ewxxPZHTD1/rM+2omspaFCszhFXSxbVCB9lfHvVOY+8ylgBqDw\nqm1mERqABusvIt7nYuqUv+xE6tDFSPkF3EPPnjq8zQGY5GoDIRCgdXMyxYe2292L\nR8/35f0ro6p9taCliGPAgB7jMWBTGTuoptEJM7Yi9+b0T8qM4Yviq9iyGjeJnwig\nY6bNd7hr2EvHw2Www/hfIsfwL4W8+D1hJtkuUHQ0s+OP+vbGxSi5N6qqM9LTuG8r\nuQA4yLJ22vf/WXqjcdyIiF5DyjHzan4KtU4tzPdGlltrjxfegF1nPzCihxKwPwKo\nDp/F0wY84v1QXp6Bxza7DaM9I+cFe4uUgTwBhDrubO41e4XOQeYHliKSzpJVM68h\nor+SEbDN1P3VfamK8uRj8sFtLrMcslCr0wnvxi7fut4ftMXRk2z7OEXdLcF9FdWT\nqczI56WmLZnM5fsfEkdGbE88i4V7As1D2OcjbhgTSiFwbcHnWQhCGd709t5c7xIG\noFIapMpqHFYvZVy/mVPCOinrW+BRyI1bbwgmY/BVbxxNAgMBAAGgADANBgkqhkiG\n9w0BAQsFAAOCAgEAH+8i1ADYuh3KgEFmEOgmhBe2waanpC7bBOaPCuLlXOhJcZnb\nTPTPVx9zBShPuaQS+xgobpRxBlataHKw2a/YoO8lPklglAcR188uRToIHjD/oeEa\nnAwOE+FZHcxj7lkEWTjKTFlCNkEuWYm2QqEpYZ9jTRw23JyU6zHHZU9TRPRBNReL\nhudzJOB4ZN+DZOOvhEUEOxNhVJDvPo9jldS7Ha0Xg8Q7AMxbmc7LYFx11/f2Oejv\nPJL3HYi2WGRp2Ie81Heu8CgX5KEt6gu8EbLmTopQsVcU9CirO5klgFtJf3YzAMYz\n4jZqbwR3N2WYGvloT2DrMvMhX4m+d0eC8I5M7JIW+GzUO7h4szvhSJ2Vw9c1rcGP\ng6Y6RxEyG/GJtaG3SII8M+nXbPG4D/ZDYlh4YP0wWxeQZimifpAPmVpYlUG1nKgC\nerHoOIw1INI9M5jgbFNEcTE/GLqficWjypq6lBeV5aZo/rXgyQFoxHI1N2lRbVfN\ndEA+2D89le6VCizjHIsTDyY9tqGkRopsvm78DEwSmCBHnxRiky9A4MTRApAKoJDJ\nQ/7AxbY7rhcWmwn+GZDoP5aY1aLe86Y1w3QR2VWKeXOKQeepRsOs8QTAPJoowpdy\nfWlZKRZdDjhiwTCNztFL72KV7x0DcLVowYRJfrrK0MQdoosBs2DR74bztEg=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "client/auth_client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"google.golang.org/grpc\"\n)\n\n// AuthClient is a client to call authentication RPC\ntype AuthClient struct {\n\tservice  pb.AuthServiceClient\n\tusername string\n\tpassword string\n}\n\n// NewAuthClient returns a new auth client\nfunc NewAuthClient(cc *grpc.ClientConn, username string, password string) *AuthClient {\n\tservice := pb.NewAuthServiceClient(cc)\n\treturn &AuthClient{service, username, password}\n}\n\n// Login login user and returns the access token\nfunc (client *AuthClient) Login() (string, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\treq := &pb.LoginRequest{\n\t\tUsername: client.username,\n\t\tPassword: client.password,\n\t}\n\n\tres, err := client.service.Login(ctx, req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn res.GetAccessToken(), nil\n}\n"
  },
  {
    "path": "client/auth_interceptor.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// AuthInterceptor is a client interceptor for authentication\ntype AuthInterceptor struct {\n\tauthClient  *AuthClient\n\tauthMethods map[string]bool\n\taccessToken string\n}\n\n// NewAuthInterceptor returns a new auth interceptor\nfunc NewAuthInterceptor(\n\tauthClient *AuthClient,\n\tauthMethods map[string]bool,\n\trefreshDuration time.Duration,\n) (*AuthInterceptor, error) {\n\tinterceptor := &AuthInterceptor{\n\t\tauthClient:  authClient,\n\t\tauthMethods: authMethods,\n\t}\n\n\terr := interceptor.scheduleRefreshToken(refreshDuration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn interceptor, nil\n}\n\n// Unary returns a client interceptor to authenticate unary RPC\nfunc (interceptor *AuthInterceptor) Unary() grpc.UnaryClientInterceptor {\n\treturn func(\n\t\tctx context.Context,\n\t\tmethod string,\n\t\treq, reply interface{},\n\t\tcc *grpc.ClientConn,\n\t\tinvoker grpc.UnaryInvoker,\n\t\topts ...grpc.CallOption,\n\t) error {\n\t\tlog.Printf(\"--> unary interceptor: %s\", method)\n\n\t\tif interceptor.authMethods[method] {\n\t\t\treturn invoker(interceptor.attachToken(ctx), method, req, reply, cc, opts...)\n\t\t}\n\n\t\treturn invoker(ctx, method, req, reply, cc, opts...)\n\t}\n}\n\n// Stream returns a client interceptor to authenticate stream RPC\nfunc (interceptor *AuthInterceptor) Stream() grpc.StreamClientInterceptor {\n\treturn func(\n\t\tctx context.Context,\n\t\tdesc *grpc.StreamDesc,\n\t\tcc *grpc.ClientConn,\n\t\tmethod string,\n\t\tstreamer grpc.Streamer,\n\t\topts ...grpc.CallOption,\n\t) (grpc.ClientStream, error) {\n\t\tlog.Printf(\"--> stream interceptor: %s\", method)\n\n\t\tif interceptor.authMethods[method] {\n\t\t\treturn streamer(interceptor.attachToken(ctx), desc, cc, method, opts...)\n\t\t}\n\n\t\treturn streamer(ctx, desc, cc, method, opts...)\n\t}\n}\n\nfunc (interceptor *AuthInterceptor) attachToken(ctx context.Context) context.Context {\n\treturn metadata.AppendToOutgoingContext(ctx, \"authorization\", interceptor.accessToken)\n}\n\nfunc (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Duration) error {\n\terr := interceptor.refreshToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\twait := refreshDuration\n\t\tfor {\n\t\t\ttime.Sleep(wait)\n\t\t\terr := interceptor.refreshToken()\n\t\t\tif err != nil {\n\t\t\t\twait = time.Second\n\t\t\t} else {\n\t\t\t\twait = refreshDuration\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (interceptor *AuthInterceptor) refreshToken() error {\n\taccessToken, err := interceptor.authClient.Login()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinterceptor.accessToken = accessToken\n\tlog.Printf(\"token refreshed: %v\", accessToken)\n\n\treturn nil\n}\n"
  },
  {
    "path": "client/laptop_client.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// LaptopClient is a client to call laptop service RPCs\ntype LaptopClient struct {\n\tservice pb.LaptopServiceClient\n}\n\n// NewLaptopClient returns a new laptop client\nfunc NewLaptopClient(cc *grpc.ClientConn) *LaptopClient {\n\tservice := pb.NewLaptopServiceClient(cc)\n\treturn &LaptopClient{service}\n}\n\n// CreateLaptop calls create laptop RPC\nfunc (laptopClient *LaptopClient) CreateLaptop(laptop *pb.Laptop) {\n\treq := &pb.CreateLaptopRequest{\n\t\tLaptop: laptop,\n\t}\n\n\t// set timeout\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tres, err := laptopClient.service.CreateLaptop(ctx, req)\n\tif err != nil {\n\t\tst, ok := status.FromError(err)\n\t\tif ok && st.Code() == codes.AlreadyExists {\n\t\t\t// not a big deal\n\t\t\tlog.Print(\"laptop already exists\")\n\t\t} else {\n\t\t\tlog.Fatal(\"cannot create laptop: \", err)\n\t\t}\n\t\treturn\n\t}\n\n\tlog.Printf(\"created laptop with id: %s\", res.Id)\n}\n\n// SearchLaptop calls search laptop RPC\nfunc (laptopClient *LaptopClient) SearchLaptop(filter *pb.Filter) {\n\tlog.Print(\"search filter: \", filter)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\treq := &pb.SearchLaptopRequest{Filter: filter}\n\tstream, err := laptopClient.service.SearchLaptop(ctx, req)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot search laptop: \", err)\n\t}\n\n\tfor {\n\t\tres, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\treturn\n\t\t}\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"cannot receive response: \", err)\n\t\t}\n\n\t\tlaptop := res.GetLaptop()\n\t\tlog.Print(\"- found: \", laptop.GetId())\n\t\tlog.Print(\"  + brand: \", laptop.GetBrand())\n\t\tlog.Print(\"  + name: \", laptop.GetName())\n\t\tlog.Print(\"  + cpu cores: \", laptop.GetCpu().GetNumberCores())\n\t\tlog.Print(\"  + cpu min ghz: \", laptop.GetCpu().GetMinGhz())\n\t\tlog.Print(\"  + ram: \", laptop.GetRam())\n\t\tlog.Print(\"  + price: \", laptop.GetPriceUsd())\n\t}\n}\n\n// UploadImage calls upload image RPC\nfunc (laptopClient *LaptopClient) UploadImage(laptopID string, imagePath string) {\n\tfile, err := os.Open(imagePath)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot open image file: \", err)\n\t}\n\tdefer file.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tstream, err := laptopClient.service.UploadImage(ctx)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot upload image: \", err)\n\t}\n\n\treq := &pb.UploadImageRequest{\n\t\tData: &pb.UploadImageRequest_Info{\n\t\t\tInfo: &pb.ImageInfo{\n\t\t\t\tLaptopId:  laptopID,\n\t\t\t\tImageType: filepath.Ext(imagePath),\n\t\t\t},\n\t\t},\n\t}\n\n\terr = stream.Send(req)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot send image info to server: \", err, stream.RecvMsg(nil))\n\t}\n\n\treader := bufio.NewReader(file)\n\tbuffer := make([]byte, 1024)\n\n\tfor {\n\t\tn, err := reader.Read(buffer)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"cannot read chunk to buffer: \", err)\n\t\t}\n\n\t\treq := &pb.UploadImageRequest{\n\t\t\tData: &pb.UploadImageRequest_ChunkData{\n\t\t\t\tChunkData: buffer[:n],\n\t\t\t},\n\t\t}\n\n\t\terr = stream.Send(req)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"cannot send chunk to server: \", err, stream.RecvMsg(nil))\n\t\t}\n\t}\n\n\tres, err := stream.CloseAndRecv()\n\tif err != nil {\n\t\tlog.Fatal(\"cannot receive response: \", err)\n\t}\n\n\tlog.Printf(\"image uploaded with id: %s, size: %d\", res.GetId(), res.GetSize())\n}\n\n// RateLaptop calls rate laptop RPC\nfunc (laptopClient *LaptopClient) RateLaptop(laptopIDs []string, scores []float64) error {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tstream, err := laptopClient.service.RateLaptop(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot rate laptop: %v\", err)\n\t}\n\n\twaitResponse := make(chan error)\n\t// go routine to receive responses\n\tgo func() {\n\t\tfor {\n\t\t\tres, err := stream.Recv()\n\t\t\tif err == io.EOF {\n\t\t\t\tlog.Print(\"no more responses\")\n\t\t\t\twaitResponse <- nil\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\twaitResponse <- fmt.Errorf(\"cannot receive stream response: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlog.Print(\"received response: \", res)\n\t\t}\n\t}()\n\n\t// send requests\n\tfor i, laptopID := range laptopIDs {\n\t\treq := &pb.RateLaptopRequest{\n\t\t\tLaptopId: laptopID,\n\t\t\tScore:    scores[i],\n\t\t}\n\n\t\terr := stream.Send(req)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"cannot send stream request: %v - %v\", err, stream.RecvMsg(nil))\n\t\t}\n\n\t\tlog.Print(\"sent request: \", req)\n\t}\n\n\terr = stream.CloseSend()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot close send: %v\", err)\n\t}\n\n\terr = <-waitResponse\n\treturn err\n}\n"
  },
  {
    "path": "cmd/client/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gitlab.com/techschool/pcbook/client\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"gitlab.com/techschool/pcbook/sample\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n)\n\nfunc testCreateLaptop(laptopClient *client.LaptopClient) {\n\tlaptopClient.CreateLaptop(sample.NewLaptop())\n}\n\nfunc testSearchLaptop(laptopClient *client.LaptopClient) {\n\tfor i := 0; i < 10; i++ {\n\t\tlaptopClient.CreateLaptop(sample.NewLaptop())\n\t}\n\n\tfilter := &pb.Filter{\n\t\tMaxPriceUsd: 3000,\n\t\tMinCpuCores: 4,\n\t\tMinCpuGhz:   2.5,\n\t\tMinRam:      &pb.Memory{Value: 8, Unit: pb.Memory_GIGABYTE},\n\t}\n\n\tlaptopClient.SearchLaptop(filter)\n}\n\nfunc testUploadImage(laptopClient *client.LaptopClient) {\n\tlaptop := sample.NewLaptop()\n\tlaptopClient.CreateLaptop(laptop)\n\tlaptopClient.UploadImage(laptop.GetId(), \"tmp/laptop.jpg\")\n}\n\nfunc testRateLaptop(laptopClient *client.LaptopClient) {\n\tn := 3\n\tlaptopIDs := make([]string, n)\n\n\tfor i := 0; i < n; i++ {\n\t\tlaptop := sample.NewLaptop()\n\t\tlaptopIDs[i] = laptop.GetId()\n\t\tlaptopClient.CreateLaptop(laptop)\n\t}\n\n\tscores := make([]float64, n)\n\tfor {\n\t\tfmt.Print(\"rate laptop (y/n)? \")\n\t\tvar answer string\n\t\tfmt.Scan(&answer)\n\n\t\tif strings.ToLower(answer) != \"y\" {\n\t\t\tbreak\n\t\t}\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tscores[i] = sample.RandomLaptopScore()\n\t\t}\n\n\t\terr := laptopClient.RateLaptop(laptopIDs, scores)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n}\n\nconst (\n\tusername        = \"admin1\"\n\tpassword        = \"secret\"\n\trefreshDuration = 30 * time.Second\n)\n\nfunc authMethods() map[string]bool {\n\tconst laptopServicePath = \"/techschool.pcbook.LaptopService/\"\n\n\treturn map[string]bool{\n\t\tlaptopServicePath + \"CreateLaptop\": true,\n\t\tlaptopServicePath + \"UploadImage\":  true,\n\t\tlaptopServicePath + \"RateLaptop\":   true,\n\t}\n}\n\nfunc loadTLSCredentials() (credentials.TransportCredentials, error) {\n\t// Load certificate of the CA who signed server's certificate\n\tpemServerCA, err := ioutil.ReadFile(\"cert/ca-cert.pem\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcertPool := x509.NewCertPool()\n\tif !certPool.AppendCertsFromPEM(pemServerCA) {\n\t\treturn nil, fmt.Errorf(\"failed to add server CA's certificate\")\n\t}\n\n\t// Load client's certificate and private key\n\tclientCert, err := tls.LoadX509KeyPair(\"cert/client-cert.pem\", \"cert/client-key.pem\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Create the credentials and return it\n\tconfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{clientCert},\n\t\tRootCAs:      certPool,\n\t}\n\n\treturn credentials.NewTLS(config), nil\n}\n\nfunc main() {\n\tserverAddress := flag.String(\"address\", \"\", \"the server address\")\n\tenableTLS := flag.Bool(\"tls\", false, \"enable SSL/TLS\")\n\tflag.Parse()\n\tlog.Printf(\"dial server %s, TLS = %t\", *serverAddress, *enableTLS)\n\n\ttransportOption := grpc.WithInsecure()\n\n\tif *enableTLS {\n\t\ttlsCredentials, err := loadTLSCredentials()\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"cannot load TLS credentials: \", err)\n\t\t}\n\n\t\ttransportOption = grpc.WithTransportCredentials(tlsCredentials)\n\t}\n\n\tcc1, err := grpc.Dial(*serverAddress, transportOption)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot dial server: \", err)\n\t}\n\n\tauthClient := client.NewAuthClient(cc1, username, password)\n\tinterceptor, err := client.NewAuthInterceptor(authClient, authMethods(), refreshDuration)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot create auth interceptor: \", err)\n\t}\n\n\tcc2, err := grpc.Dial(\n\t\t*serverAddress,\n\t\ttransportOption,\n\t\tgrpc.WithUnaryInterceptor(interceptor.Unary()),\n\t\tgrpc.WithStreamInterceptor(interceptor.Stream()),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot dial server: \", err)\n\t}\n\n\tlaptopClient := client.NewLaptopClient(cc2)\n\ttestRateLaptop(laptopClient)\n}\n"
  },
  {
    "path": "cmd/server/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/runtime\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"gitlab.com/techschool/pcbook/service\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/reflection\"\n)\n\nfunc seedUsers(userStore service.UserStore) error {\n\terr := createUser(userStore, \"admin1\", \"secret\", \"admin\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn createUser(userStore, \"user1\", \"secret\", \"user\")\n}\n\nfunc createUser(userStore service.UserStore, username, password, role string) error {\n\tuser, err := service.NewUser(username, password, role)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn userStore.Save(user)\n}\n\nconst (\n\tsecretKey     = \"secret\"\n\ttokenDuration = 15 * time.Minute\n)\n\nconst (\n\tserverCertFile   = \"cert/server-cert.pem\"\n\tserverKeyFile    = \"cert/server-key.pem\"\n\tclientCACertFile = \"cert/ca-cert.pem\"\n)\n\nfunc accessibleRoles() map[string][]string {\n\tconst laptopServicePath = \"/techschool.pcbook.LaptopService/\"\n\n\treturn map[string][]string{\n\t\tlaptopServicePath + \"CreateLaptop\": {\"admin\"},\n\t\tlaptopServicePath + \"UploadImage\":  {\"admin\"},\n\t\tlaptopServicePath + \"RateLaptop\":   {\"admin\", \"user\"},\n\t}\n}\n\nfunc loadTLSCredentials() (credentials.TransportCredentials, error) {\n\t// Load certificate of the CA who signed client's certificate\n\tpemClientCA, err := ioutil.ReadFile(clientCACertFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcertPool := x509.NewCertPool()\n\tif !certPool.AppendCertsFromPEM(pemClientCA) {\n\t\treturn nil, fmt.Errorf(\"failed to add client CA's certificate\")\n\t}\n\n\t// Load server's certificate and private key\n\tserverCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Create the credentials and return it\n\tconfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{serverCert},\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    certPool,\n\t}\n\n\treturn credentials.NewTLS(config), nil\n}\n\nfunc runGRPCServer(\n\tauthServer pb.AuthServiceServer,\n\tlaptopServer pb.LaptopServiceServer,\n\tjwtManager *service.JWTManager,\n\tenableTLS bool,\n\tlistener net.Listener,\n) error {\n\tinterceptor := service.NewAuthInterceptor(jwtManager, accessibleRoles())\n\tserverOptions := []grpc.ServerOption{\n\t\tgrpc.UnaryInterceptor(interceptor.Unary()),\n\t\tgrpc.StreamInterceptor(interceptor.Stream()),\n\t}\n\n\tif enableTLS {\n\t\ttlsCredentials, err := loadTLSCredentials()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"cannot load TLS credentials: %w\", err)\n\t\t}\n\n\t\tserverOptions = append(serverOptions, grpc.Creds(tlsCredentials))\n\t}\n\n\tgrpcServer := grpc.NewServer(serverOptions...)\n\n\tpb.RegisterAuthServiceServer(grpcServer, authServer)\n\tpb.RegisterLaptopServiceServer(grpcServer, laptopServer)\n\treflection.Register(grpcServer)\n\n\tlog.Printf(\"Start GRPC server at %s, TLS = %t\", listener.Addr().String(), enableTLS)\n\treturn grpcServer.Serve(listener)\n}\n\nfunc runRESTServer(\n\tauthServer pb.AuthServiceServer,\n\tlaptopServer pb.LaptopServiceServer,\n\tjwtManager *service.JWTManager,\n\tenableTLS bool,\n\tlistener net.Listener,\n\tgrpcEndpoint string,\n) error {\n\tmux := runtime.NewServeMux()\n\tdialOptions := []grpc.DialOption{grpc.WithInsecure()}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\t// in-process handler\n\t// err := pb.RegisterAuthServiceHandlerServer(ctx, mux, authServer)\n\n\terr := pb.RegisterAuthServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, dialOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// in-process handler\n\t// err = pb.RegisterLaptopServiceHandlerServer(ctx, mux, laptopServer)\n\n\terr = pb.RegisterLaptopServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, dialOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"Start REST server at %s, TLS = %t\", listener.Addr().String(), enableTLS)\n\tif enableTLS {\n\t\treturn http.ServeTLS(listener, mux, serverCertFile, serverKeyFile)\n\t}\n\treturn http.Serve(listener, mux)\n}\n\nfunc main() {\n\tport := flag.Int(\"port\", 0, \"the server port\")\n\tenableTLS := flag.Bool(\"tls\", false, \"enable SSL/TLS\")\n\tserverType := flag.String(\"type\", \"grpc\", \"type of server (grpc/rest)\")\n\tendPoint := flag.String(\"endpoint\", \"\", \"gRPC endpoint\")\n\tflag.Parse()\n\n\tuserStore := service.NewInMemoryUserStore()\n\terr := seedUsers(userStore)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot seed users: \", err)\n\t}\n\n\tjwtManager := service.NewJWTManager(secretKey, tokenDuration)\n\tauthServer := service.NewAuthServer(userStore, jwtManager)\n\n\tlaptopStore := service.NewInMemoryLaptopStore()\n\timageStore := service.NewDiskImageStore(\"img\")\n\tratingStore := service.NewInMemoryRatingStore()\n\tlaptopServer := service.NewLaptopServer(laptopStore, imageStore, ratingStore)\n\n\taddress := fmt.Sprintf(\"0.0.0.0:%d\", *port)\n\tlistener, err := net.Listen(\"tcp\", address)\n\tif err != nil {\n\t\tlog.Fatal(\"cannot start server: \", err)\n\t}\n\n\tif *serverType == \"grpc\" {\n\t\terr = runGRPCServer(authServer, laptopServer, jwtManager, *enableTLS, listener)\n\t} else {\n\t\terr = runRESTServer(authServer, laptopServer, jwtManager, *enableTLS, listener, *endPoint)\n\t}\n\n\tif err != nil {\n\t\tlog.Fatal(\"cannot start server: \", err)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module gitlab.com/techschool/pcbook\n\ngo 1.15\n\nrequire (\n\tgithub.com/dgrijalva/jwt-go v3.2.0+incompatible\n\tgithub.com/golang/protobuf v1.4.3\n\tgithub.com/google/uuid v1.2.0\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0\n\tgithub.com/jinzhu/copier v0.2.3\n\tgithub.com/stretchr/testify v1.7.0\n\tgolang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad\n\tgoogle.golang.org/genproto v0.0.0-20210202153253-cf70463f6119\n\tgoogle.golang.org/grpc v1.35.0\n\tgoogle.golang.org/protobuf v1.25.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0 h1:EhTvIsn53GrBLl45YVHk25cUHQHwlJfq2y8b7W5IpVY=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0/go.mod h1:ly5QWKtiqC7tGfzgXYtpoZYmEWx5Z82/b18ASEL+yGc=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jinzhu/copier v0.2.3 h1:Oe09ju+9qft7TffZ7l/04AB2f8u1+V4ZMxmp/nnqeOs=\ngithub.com/jinzhu/copier v0.2.3/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=\ngolang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24 h1:R8bzl0244nw47n1xKs1MUMAaTNgjavKcN/aX2Ss3+Fo=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210106152847-07624b53cd92 h1:jOTk2Z6KYaWoptUFqZ167cS8peoUPjFEXrsqfVkkCGc=\ngoogle.golang.org/genproto v0.0.0-20210106152847-07624b53cd92/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210202153253-cf70463f6119 h1:m9+RjTMas6brUP8DBxSAa/WIPFy7FIhKpvk+9Ppce8E=\ngoogle.golang.org/genproto v0.0.0-20210202153253-cf70463f6119/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "img/.gitkeep",
    "content": ""
  },
  {
    "path": "nginx.conf",
    "content": "worker_processes  1;\n\nerror_log  /usr/local/var/log/nginx/error.log;\n\nevents {\n    worker_connections  10;\n}\n\nhttp {\n    access_log  /usr/local/var/log/nginx/access.log;\n\n    upstream auth_services {\n        server 0.0.0.0:50051;\n        server 0.0.0.0:50052;\n    }\n\n    upstream laptop_services {\n        server 0.0.0.0:50051;\n        server 0.0.0.0:50052;\n    }\n\n    server {\n        listen       8080 ssl http2;\n\n        # Mutual TLS between gRPC client and nginx\n        ssl_certificate cert/server-cert.pem;\n        ssl_certificate_key cert/server-key.pem;\n\n        ssl_client_certificate cert/ca-cert.pem;\n        ssl_verify_client on;\n\n        location /techschool.pcbook.AuthService {\n            grpc_pass grpcs://auth_services;\n\n            # Mutual TLS between nginx and gRPC server\n            grpc_ssl_certificate cert/server-cert.pem;\n            grpc_ssl_certificate_key cert/server-key.pem;\n        }\n\n        location /techschool.pcbook.LaptopService {\n            grpc_pass grpcs://laptop_services;\n\n            # Mutual TLS between nginx and gRPC server\n            grpc_ssl_certificate cert/server-cert.pem;\n            grpc_ssl_certificate_key cert/server-key.pem;\n        }\n    }\n}\n"
  },
  {
    "path": "pb/auth_service.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: auth_service.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\t_ \"google.golang.org/genproto/googleapis/api/annotations\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype LoginRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUsername string `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword string `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n}\n\nfunc (x *LoginRequest) Reset() {\n\t*x = LoginRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_auth_service_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoginRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoginRequest) ProtoMessage() {}\n\nfunc (x *LoginRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_auth_service_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.\nfunc (*LoginRequest) Descriptor() ([]byte, []int) {\n\treturn file_auth_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *LoginRequest) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *LoginRequest) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\ntype LoginResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAccessToken string `protobuf:\"bytes,1,opt,name=access_token,json=accessToken,proto3\" json:\"access_token,omitempty\"`\n}\n\nfunc (x *LoginResponse) Reset() {\n\t*x = LoginResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_auth_service_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoginResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoginResponse) ProtoMessage() {}\n\nfunc (x *LoginResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_auth_service_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead.\nfunc (*LoginResponse) Descriptor() ([]byte, []int) {\n\treturn file_auth_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *LoginResponse) GetAccessToken() string {\n\tif x != nil {\n\t\treturn x.AccessToken\n\t}\n\treturn \"\"\n}\n\nvar File_auth_service_proto protoreflect.FileDescriptor\n\nvar file_auth_service_proto_rawDesc = []byte{\n\t0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,\n\t0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x46, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x32, 0x0a,\n\t0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21,\n\t0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65,\n\t0x6e, 0x32, 0x74, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x12, 0x65, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x63, 0x68,\n\t0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c, 0x6f,\n\t0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x74, 0x65, 0x63,\n\t0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c,\n\t0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3,\n\t0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x6c,\n\t0x6f, 0x67, 0x69, 0x6e, 0x3a, 0x01, 0x2a, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67,\n\t0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b,\n\t0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_auth_service_proto_rawDescOnce sync.Once\n\tfile_auth_service_proto_rawDescData = file_auth_service_proto_rawDesc\n)\n\nfunc file_auth_service_proto_rawDescGZIP() []byte {\n\tfile_auth_service_proto_rawDescOnce.Do(func() {\n\t\tfile_auth_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_service_proto_rawDescData)\n\t})\n\treturn file_auth_service_proto_rawDescData\n}\n\nvar file_auth_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_auth_service_proto_goTypes = []interface{}{\n\t(*LoginRequest)(nil),  // 0: techschool.pcbook.LoginRequest\n\t(*LoginResponse)(nil), // 1: techschool.pcbook.LoginResponse\n}\nvar file_auth_service_proto_depIdxs = []int32{\n\t0, // 0: techschool.pcbook.AuthService.Login:input_type -> techschool.pcbook.LoginRequest\n\t1, // 1: techschool.pcbook.AuthService.Login:output_type -> techschool.pcbook.LoginResponse\n\t1, // [1:2] is the sub-list for method output_type\n\t0, // [0:1] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_auth_service_proto_init() }\nfunc file_auth_service_proto_init() {\n\tif File_auth_service_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_auth_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoginRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_auth_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoginResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_auth_service_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_auth_service_proto_goTypes,\n\t\tDependencyIndexes: file_auth_service_proto_depIdxs,\n\t\tMessageInfos:      file_auth_service_proto_msgTypes,\n\t}.Build()\n\tFile_auth_service_proto = out.File\n\tfile_auth_service_proto_rawDesc = nil\n\tfile_auth_service_proto_goTypes = nil\n\tfile_auth_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/auth_service.pb.gw.go",
    "content": "// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.\n// source: auth_service.proto\n\n/*\nPackage pb is a reverse proxy.\n\nIt translates gRPC into RESTful JSON APIs.\n*/\npackage pb\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/runtime\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/utilities\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/grpclog\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// Suppress \"imported and not used\" errors\nvar _ codes.Code\nvar _ io.Reader\nvar _ status.Status\nvar _ = runtime.String\nvar _ = utilities.NewDoubleArray\nvar _ = metadata.Join\n\nfunc request_AuthService_Login_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq LoginRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.Login(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc 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) {\n\tvar protoReq LoginRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.Login(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\n// RegisterAuthServiceHandlerServer registers the http handlers for service AuthService to \"mux\".\n// UnaryRPC     :call AuthServiceServer directly.\n// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.\n// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthServiceHandlerFromEndpoint instead.\nfunc RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer) error {\n\n\tmux.Handle(\"POST\", pattern_AuthService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, \"/techschool.pcbook.AuthService/Login\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_AuthService_Login_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\n// RegisterAuthServiceHandlerFromEndpoint is same as RegisterAuthServiceHandler but\n// automatically dials to \"endpoint\" and closes the connection when \"ctx\" gets done.\nfunc RegisterAuthServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {\n\tconn, err := grpc.Dial(endpoint, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\n\treturn RegisterAuthServiceHandler(ctx, mux, conn)\n}\n\n// RegisterAuthServiceHandler registers the http handlers for service AuthService to \"mux\".\n// The handlers forward requests to the grpc endpoint over \"conn\".\nfunc RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {\n\treturn RegisterAuthServiceHandlerClient(ctx, mux, NewAuthServiceClient(conn))\n}\n\n// RegisterAuthServiceHandlerClient registers the http handlers for service AuthService\n// to \"mux\". The handlers forward requests to the grpc endpoint over the given implementation of \"AuthServiceClient\".\n// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in \"AuthServiceClient\"\n// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in\n// \"AuthServiceClient\" to call the correct interceptors.\nfunc RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthServiceClient) error {\n\n\tmux.Handle(\"POST\", pattern_AuthService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req, \"/techschool.pcbook.AuthService/Login\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_AuthService_Login_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_AuthService_Login_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\nvar (\n\tpattern_AuthService_Login_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"v1\", \"auth\", \"login\"}, \"\"))\n)\n\nvar (\n\tforward_AuthService_Login_0 = runtime.ForwardResponseMessage\n)\n"
  },
  {
    "path": "pb/auth_service_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage pb\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// AuthServiceClient is the client API for AuthService service.\n//\n// 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.\ntype AuthServiceClient interface {\n\tLogin(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)\n}\n\ntype authServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient {\n\treturn &authServiceClient{cc}\n}\n\nfunc (c *authServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {\n\tout := new(LoginResponse)\n\terr := c.cc.Invoke(ctx, \"/techschool.pcbook.AuthService/Login\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AuthServiceServer is the server API for AuthService service.\n// All implementations must embed UnimplementedAuthServiceServer\n// for forward compatibility\ntype AuthServiceServer interface {\n\tLogin(context.Context, *LoginRequest) (*LoginResponse, error)\n\tmustEmbedUnimplementedAuthServiceServer()\n}\n\n// UnimplementedAuthServiceServer must be embedded to have forward compatible implementations.\ntype UnimplementedAuthServiceServer struct {\n}\n\nfunc (UnimplementedAuthServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Login not implemented\")\n}\nfunc (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}\n\n// UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AuthServiceServer will\n// result in compilation errors.\ntype UnsafeAuthServiceServer interface {\n\tmustEmbedUnimplementedAuthServiceServer()\n}\n\nfunc RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {\n\ts.RegisterService(&AuthService_ServiceDesc, srv)\n}\n\nfunc _AuthService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LoginRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AuthServiceServer).Login(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/techschool.pcbook.AuthService/Login\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AuthServiceServer).Login(ctx, req.(*LoginRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar AuthService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"techschool.pcbook.AuthService\",\n\tHandlerType: (*AuthServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Login\",\n\t\t\tHandler:    _AuthService_Login_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"auth_service.proto\",\n}\n"
  },
  {
    "path": "pb/filter_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: filter_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Filter struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tMaxPriceUsd float64 `protobuf:\"fixed64,1,opt,name=max_price_usd,json=maxPriceUsd,proto3\" json:\"max_price_usd,omitempty\"`\n\tMinCpuCores uint32  `protobuf:\"varint,2,opt,name=min_cpu_cores,json=minCpuCores,proto3\" json:\"min_cpu_cores,omitempty\"`\n\tMinCpuGhz   float64 `protobuf:\"fixed64,3,opt,name=min_cpu_ghz,json=minCpuGhz,proto3\" json:\"min_cpu_ghz,omitempty\"`\n\tMinRam      *Memory `protobuf:\"bytes,4,opt,name=min_ram,json=minRam,proto3\" json:\"min_ram,omitempty\"`\n}\n\nfunc (x *Filter) Reset() {\n\t*x = Filter{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_filter_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Filter) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Filter) ProtoMessage() {}\n\nfunc (x *Filter) ProtoReflect() protoreflect.Message {\n\tmi := &file_filter_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Filter.ProtoReflect.Descriptor instead.\nfunc (*Filter) Descriptor() ([]byte, []int) {\n\treturn file_filter_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Filter) GetMaxPriceUsd() float64 {\n\tif x != nil {\n\t\treturn x.MaxPriceUsd\n\t}\n\treturn 0\n}\n\nfunc (x *Filter) GetMinCpuCores() uint32 {\n\tif x != nil {\n\t\treturn x.MinCpuCores\n\t}\n\treturn 0\n}\n\nfunc (x *Filter) GetMinCpuGhz() float64 {\n\tif x != nil {\n\t\treturn x.MinCpuGhz\n\t}\n\treturn 0\n}\n\nfunc (x *Filter) GetMinRam() *Memory {\n\tif x != nil {\n\t\treturn x.MinRam\n\t}\n\treturn nil\n}\n\nvar File_filter_message_proto protoreflect.FileDescriptor\n\nvar file_filter_message_proto_rawDesc = []byte{\n\t0x0a, 0x14, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 0x72,\n\t0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,\n\t0xa4, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61,\n\t0x78, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x72, 0x69, 0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x22,\n\t0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x43, 0x70, 0x75, 0x43, 0x6f, 0x72,\n\t0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x67, 0x68,\n\t0x7a, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x43, 0x70, 0x75, 0x47,\n\t0x68, 0x7a, 0x12, 0x32, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x6d, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x06,\n\t0x6d, 0x69, 0x6e, 0x52, 0x61, 0x6d, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69,\n\t0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e,\n\t0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70,\n\t0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_filter_message_proto_rawDescOnce sync.Once\n\tfile_filter_message_proto_rawDescData = file_filter_message_proto_rawDesc\n)\n\nfunc file_filter_message_proto_rawDescGZIP() []byte {\n\tfile_filter_message_proto_rawDescOnce.Do(func() {\n\t\tfile_filter_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_filter_message_proto_rawDescData)\n\t})\n\treturn file_filter_message_proto_rawDescData\n}\n\nvar file_filter_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_filter_message_proto_goTypes = []interface{}{\n\t(*Filter)(nil), // 0: techschool.pcbook.Filter\n\t(*Memory)(nil), // 1: techschool.pcbook.Memory\n}\nvar file_filter_message_proto_depIdxs = []int32{\n\t1, // 0: techschool.pcbook.Filter.min_ram:type_name -> techschool.pcbook.Memory\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_filter_message_proto_init() }\nfunc file_filter_message_proto_init() {\n\tif File_filter_message_proto != nil {\n\t\treturn\n\t}\n\tfile_memory_message_proto_init()\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_filter_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Filter); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_filter_message_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_filter_message_proto_goTypes,\n\t\tDependencyIndexes: file_filter_message_proto_depIdxs,\n\t\tMessageInfos:      file_filter_message_proto_msgTypes,\n\t}.Build()\n\tFile_filter_message_proto = out.File\n\tfile_filter_message_proto_rawDesc = nil\n\tfile_filter_message_proto_goTypes = nil\n\tfile_filter_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/keyboard_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: keyboard_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Keyboard_Layout int32\n\nconst (\n\tKeyboard_UNKNOWN Keyboard_Layout = 0\n\tKeyboard_QWERTY  Keyboard_Layout = 1\n\tKeyboard_QWERTZ  Keyboard_Layout = 2\n\tKeyboard_AZERTY  Keyboard_Layout = 3\n)\n\n// Enum value maps for Keyboard_Layout.\nvar (\n\tKeyboard_Layout_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"QWERTY\",\n\t\t2: \"QWERTZ\",\n\t\t3: \"AZERTY\",\n\t}\n\tKeyboard_Layout_value = map[string]int32{\n\t\t\"UNKNOWN\": 0,\n\t\t\"QWERTY\":  1,\n\t\t\"QWERTZ\":  2,\n\t\t\"AZERTY\":  3,\n\t}\n)\n\nfunc (x Keyboard_Layout) Enum() *Keyboard_Layout {\n\tp := new(Keyboard_Layout)\n\t*p = x\n\treturn p\n}\n\nfunc (x Keyboard_Layout) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Keyboard_Layout) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_keyboard_message_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Keyboard_Layout) Type() protoreflect.EnumType {\n\treturn &file_keyboard_message_proto_enumTypes[0]\n}\n\nfunc (x Keyboard_Layout) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Keyboard_Layout.Descriptor instead.\nfunc (Keyboard_Layout) EnumDescriptor() ([]byte, []int) {\n\treturn file_keyboard_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Keyboard struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLayout  Keyboard_Layout `protobuf:\"varint,1,opt,name=layout,proto3,enum=techschool.pcbook.Keyboard_Layout\" json:\"layout,omitempty\"`\n\tBacklit bool            `protobuf:\"varint,2,opt,name=backlit,proto3\" json:\"backlit,omitempty\"`\n}\n\nfunc (x *Keyboard) Reset() {\n\t*x = Keyboard{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_keyboard_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Keyboard) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Keyboard) ProtoMessage() {}\n\nfunc (x *Keyboard) ProtoReflect() protoreflect.Message {\n\tmi := &file_keyboard_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Keyboard.ProtoReflect.Descriptor instead.\nfunc (*Keyboard) Descriptor() ([]byte, []int) {\n\treturn file_keyboard_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Keyboard) GetLayout() Keyboard_Layout {\n\tif x != nil {\n\t\treturn x.Layout\n\t}\n\treturn Keyboard_UNKNOWN\n}\n\nfunc (x *Keyboard) GetBacklit() bool {\n\tif x != nil {\n\t\treturn x.Backlit\n\t}\n\treturn false\n}\n\nvar File_keyboard_message_proto protoreflect.FileDescriptor\n\nvar file_keyboard_message_proto_rawDesc = []byte{\n\t0x0a, 0x16, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,\n\t0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63,\n\t0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0x9b, 0x01, 0x0a, 0x08,\n\t0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x79, 0x6f,\n\t0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4b, 0x65, 0x79,\n\t0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x52, 0x06, 0x6c, 0x61,\n\t0x79, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x74, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x74, 0x22, 0x39,\n\t0x0a, 0x06, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,\n\t0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x10,\n\t0x01, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x57, 0x45, 0x52, 0x54, 0x5a, 0x10, 0x02, 0x12, 0x0a, 0x0a,\n\t0x06, 0x41, 0x5a, 0x45, 0x52, 0x54, 0x59, 0x10, 0x03, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d,\n\t0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04,\n\t0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_keyboard_message_proto_rawDescOnce sync.Once\n\tfile_keyboard_message_proto_rawDescData = file_keyboard_message_proto_rawDesc\n)\n\nfunc file_keyboard_message_proto_rawDescGZIP() []byte {\n\tfile_keyboard_message_proto_rawDescOnce.Do(func() {\n\t\tfile_keyboard_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_keyboard_message_proto_rawDescData)\n\t})\n\treturn file_keyboard_message_proto_rawDescData\n}\n\nvar file_keyboard_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_keyboard_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_keyboard_message_proto_goTypes = []interface{}{\n\t(Keyboard_Layout)(0), // 0: techschool.pcbook.Keyboard.Layout\n\t(*Keyboard)(nil),     // 1: techschool.pcbook.Keyboard\n}\nvar file_keyboard_message_proto_depIdxs = []int32{\n\t0, // 0: techschool.pcbook.Keyboard.layout:type_name -> techschool.pcbook.Keyboard.Layout\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_keyboard_message_proto_init() }\nfunc file_keyboard_message_proto_init() {\n\tif File_keyboard_message_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_keyboard_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Keyboard); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_keyboard_message_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_keyboard_message_proto_goTypes,\n\t\tDependencyIndexes: file_keyboard_message_proto_depIdxs,\n\t\tEnumInfos:         file_keyboard_message_proto_enumTypes,\n\t\tMessageInfos:      file_keyboard_message_proto_msgTypes,\n\t}.Build()\n\tFile_keyboard_message_proto = out.File\n\tfile_keyboard_message_proto_rawDesc = nil\n\tfile_keyboard_message_proto_goTypes = nil\n\tfile_keyboard_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/laptop_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: laptop_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Laptop struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId       string     `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tBrand    string     `protobuf:\"bytes,2,opt,name=brand,proto3\" json:\"brand,omitempty\"`\n\tName     string     `protobuf:\"bytes,3,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tCpu      *CPU       `protobuf:\"bytes,4,opt,name=cpu,proto3\" json:\"cpu,omitempty\"`\n\tRam      *Memory    `protobuf:\"bytes,5,opt,name=ram,proto3\" json:\"ram,omitempty\"`\n\tGpus     []*GPU     `protobuf:\"bytes,6,rep,name=gpus,proto3\" json:\"gpus,omitempty\"`\n\tStorages []*Storage `protobuf:\"bytes,7,rep,name=storages,proto3\" json:\"storages,omitempty\"`\n\tScreen   *Screen    `protobuf:\"bytes,8,opt,name=screen,proto3\" json:\"screen,omitempty\"`\n\tKeyboard *Keyboard  `protobuf:\"bytes,9,opt,name=keyboard,proto3\" json:\"keyboard,omitempty\"`\n\t// Types that are assignable to Weight:\n\t//\t*Laptop_WeightKg\n\t//\t*Laptop_WeightLb\n\tWeight      isLaptop_Weight        `protobuf_oneof:\"weight\"`\n\tPriceUsd    float64                `protobuf:\"fixed64,12,opt,name=price_usd,json=priceUsd,proto3\" json:\"price_usd,omitempty\"`\n\tReleaseYear uint32                 `protobuf:\"varint,13,opt,name=release_year,json=releaseYear,proto3\" json:\"release_year,omitempty\"`\n\tUpdatedAt   *timestamppb.Timestamp `protobuf:\"bytes,14,opt,name=updated_at,json=updatedAt,proto3\" json:\"updated_at,omitempty\"`\n}\n\nfunc (x *Laptop) Reset() {\n\t*x = Laptop{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Laptop) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Laptop) ProtoMessage() {}\n\nfunc (x *Laptop) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Laptop.ProtoReflect.Descriptor instead.\nfunc (*Laptop) Descriptor() ([]byte, []int) {\n\treturn file_laptop_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Laptop) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Laptop) GetBrand() string {\n\tif x != nil {\n\t\treturn x.Brand\n\t}\n\treturn \"\"\n}\n\nfunc (x *Laptop) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Laptop) GetCpu() *CPU {\n\tif x != nil {\n\t\treturn x.Cpu\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetRam() *Memory {\n\tif x != nil {\n\t\treturn x.Ram\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetGpus() []*GPU {\n\tif x != nil {\n\t\treturn x.Gpus\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetStorages() []*Storage {\n\tif x != nil {\n\t\treturn x.Storages\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetScreen() *Screen {\n\tif x != nil {\n\t\treturn x.Screen\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetKeyboard() *Keyboard {\n\tif x != nil {\n\t\treturn x.Keyboard\n\t}\n\treturn nil\n}\n\nfunc (m *Laptop) GetWeight() isLaptop_Weight {\n\tif m != nil {\n\t\treturn m.Weight\n\t}\n\treturn nil\n}\n\nfunc (x *Laptop) GetWeightKg() float64 {\n\tif x, ok := x.GetWeight().(*Laptop_WeightKg); ok {\n\t\treturn x.WeightKg\n\t}\n\treturn 0\n}\n\nfunc (x *Laptop) GetWeightLb() float64 {\n\tif x, ok := x.GetWeight().(*Laptop_WeightLb); ok {\n\t\treturn x.WeightLb\n\t}\n\treturn 0\n}\n\nfunc (x *Laptop) GetPriceUsd() float64 {\n\tif x != nil {\n\t\treturn x.PriceUsd\n\t}\n\treturn 0\n}\n\nfunc (x *Laptop) GetReleaseYear() uint32 {\n\tif x != nil {\n\t\treturn x.ReleaseYear\n\t}\n\treturn 0\n}\n\nfunc (x *Laptop) GetUpdatedAt() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.UpdatedAt\n\t}\n\treturn nil\n}\n\ntype isLaptop_Weight interface {\n\tisLaptop_Weight()\n}\n\ntype Laptop_WeightKg struct {\n\tWeightKg float64 `protobuf:\"fixed64,10,opt,name=weight_kg,json=weightKg,proto3,oneof\"`\n}\n\ntype Laptop_WeightLb struct {\n\tWeightLb float64 `protobuf:\"fixed64,11,opt,name=weight_lb,json=weightLb,proto3,oneof\"`\n}\n\nfunc (*Laptop_WeightKg) isLaptop_Weight() {}\n\nfunc (*Laptop_WeightLb) isLaptop_Weight() {}\n\nvar File_laptop_message_proto protoreflect.FileDescriptor\n\nvar file_laptop_message_proto_rawDesc = []byte{\n\t0x0a, 0x14, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x65,\n\t0x73, 0x73, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x1a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,\n\t0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,\n\t0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,\n\t0x14, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f,\n\t0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac,\n\t0x04, 0x0a, 0x06, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 0x61,\n\t0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x12,\n\t0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,\n\t0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x16, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63,\n\t0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x50, 0x55, 0x52, 0x03, 0x63, 0x70, 0x75, 0x12, 0x2b, 0x0a,\n\t0x03, 0x72, 0x61, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63,\n\t0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d,\n\t0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x03, 0x72, 0x61, 0x6d, 0x12, 0x2a, 0x0a, 0x04, 0x67, 0x70,\n\t0x75, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x47, 0x50, 0x55,\n\t0x52, 0x04, 0x67, 0x70, 0x75, 0x73, 0x12, 0x36, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,\n\t0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x74, 0x6f,\n\t0x72, 0x61, 0x67, 0x65, 0x52, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x73, 0x12, 0x31,\n\t0x0a, 0x06, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,\n\t0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f,\n\t0x6f, 0x6b, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x52, 0x06, 0x73, 0x63, 0x72, 0x65, 0x65,\n\t0x6e, 0x12, 0x37, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x18, 0x09, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64,\n\t0x52, 0x08, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x09, 0x77, 0x65,\n\t0x69, 0x67, 0x68, 0x74, 0x5f, 0x6b, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52,\n\t0x08, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4b, 0x67, 0x12, 0x1d, 0x0a, 0x09, 0x77, 0x65, 0x69,\n\t0x67, 0x68, 0x74, 0x5f, 0x6c, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x08,\n\t0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63,\n\t0x65, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x69,\n\t0x63, 0x65, 0x55, 0x73, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,\n\t0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x72, 0x65, 0x6c,\n\t0x65, 0x61, 0x73, 0x65, 0x59, 0x65, 0x61, 0x72, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61,\n\t0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,\n\t0x64, 0x41, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x29, 0x0a,\n\t0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68,\n\t0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62,\n\t0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_laptop_message_proto_rawDescOnce sync.Once\n\tfile_laptop_message_proto_rawDescData = file_laptop_message_proto_rawDesc\n)\n\nfunc file_laptop_message_proto_rawDescGZIP() []byte {\n\tfile_laptop_message_proto_rawDescOnce.Do(func() {\n\t\tfile_laptop_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_laptop_message_proto_rawDescData)\n\t})\n\treturn file_laptop_message_proto_rawDescData\n}\n\nvar file_laptop_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_laptop_message_proto_goTypes = []interface{}{\n\t(*Laptop)(nil),                // 0: techschool.pcbook.Laptop\n\t(*CPU)(nil),                   // 1: techschool.pcbook.CPU\n\t(*Memory)(nil),                // 2: techschool.pcbook.Memory\n\t(*GPU)(nil),                   // 3: techschool.pcbook.GPU\n\t(*Storage)(nil),               // 4: techschool.pcbook.Storage\n\t(*Screen)(nil),                // 5: techschool.pcbook.Screen\n\t(*Keyboard)(nil),              // 6: techschool.pcbook.Keyboard\n\t(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp\n}\nvar file_laptop_message_proto_depIdxs = []int32{\n\t1, // 0: techschool.pcbook.Laptop.cpu:type_name -> techschool.pcbook.CPU\n\t2, // 1: techschool.pcbook.Laptop.ram:type_name -> techschool.pcbook.Memory\n\t3, // 2: techschool.pcbook.Laptop.gpus:type_name -> techschool.pcbook.GPU\n\t4, // 3: techschool.pcbook.Laptop.storages:type_name -> techschool.pcbook.Storage\n\t5, // 4: techschool.pcbook.Laptop.screen:type_name -> techschool.pcbook.Screen\n\t6, // 5: techschool.pcbook.Laptop.keyboard:type_name -> techschool.pcbook.Keyboard\n\t7, // 6: techschool.pcbook.Laptop.updated_at:type_name -> google.protobuf.Timestamp\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_laptop_message_proto_init() }\nfunc file_laptop_message_proto_init() {\n\tif File_laptop_message_proto != nil {\n\t\treturn\n\t}\n\tfile_processor_message_proto_init()\n\tfile_memory_message_proto_init()\n\tfile_storage_message_proto_init()\n\tfile_screen_message_proto_init()\n\tfile_keyboard_message_proto_init()\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_laptop_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Laptop); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tfile_laptop_message_proto_msgTypes[0].OneofWrappers = []interface{}{\n\t\t(*Laptop_WeightKg)(nil),\n\t\t(*Laptop_WeightLb)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_laptop_message_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_laptop_message_proto_goTypes,\n\t\tDependencyIndexes: file_laptop_message_proto_depIdxs,\n\t\tMessageInfos:      file_laptop_message_proto_msgTypes,\n\t}.Build()\n\tFile_laptop_message_proto = out.File\n\tfile_laptop_message_proto_rawDesc = nil\n\tfile_laptop_message_proto_goTypes = nil\n\tfile_laptop_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/laptop_service.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: laptop_service.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\t_ \"google.golang.org/genproto/googleapis/api/annotations\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype CreateLaptopRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLaptop *Laptop `protobuf:\"bytes,1,opt,name=laptop,proto3\" json:\"laptop,omitempty\"`\n}\n\nfunc (x *CreateLaptopRequest) Reset() {\n\t*x = CreateLaptopRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateLaptopRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateLaptopRequest) ProtoMessage() {}\n\nfunc (x *CreateLaptopRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateLaptopRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateLaptopRequest) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CreateLaptopRequest) GetLaptop() *Laptop {\n\tif x != nil {\n\t\treturn x.Laptop\n\t}\n\treturn nil\n}\n\ntype CreateLaptopResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n}\n\nfunc (x *CreateLaptopResponse) Reset() {\n\t*x = CreateLaptopResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CreateLaptopResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateLaptopResponse) ProtoMessage() {}\n\nfunc (x *CreateLaptopResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateLaptopResponse.ProtoReflect.Descriptor instead.\nfunc (*CreateLaptopResponse) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CreateLaptopResponse) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype SearchLaptopRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFilter *Filter `protobuf:\"bytes,1,opt,name=filter,proto3\" json:\"filter,omitempty\"`\n}\n\nfunc (x *SearchLaptopRequest) Reset() {\n\t*x = SearchLaptopRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchLaptopRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchLaptopRequest) ProtoMessage() {}\n\nfunc (x *SearchLaptopRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchLaptopRequest.ProtoReflect.Descriptor instead.\nfunc (*SearchLaptopRequest) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *SearchLaptopRequest) GetFilter() *Filter {\n\tif x != nil {\n\t\treturn x.Filter\n\t}\n\treturn nil\n}\n\ntype SearchLaptopResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLaptop *Laptop `protobuf:\"bytes,1,opt,name=laptop,proto3\" json:\"laptop,omitempty\"`\n}\n\nfunc (x *SearchLaptopResponse) Reset() {\n\t*x = SearchLaptopResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SearchLaptopResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SearchLaptopResponse) ProtoMessage() {}\n\nfunc (x *SearchLaptopResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SearchLaptopResponse.ProtoReflect.Descriptor instead.\nfunc (*SearchLaptopResponse) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *SearchLaptopResponse) GetLaptop() *Laptop {\n\tif x != nil {\n\t\treturn x.Laptop\n\t}\n\treturn nil\n}\n\ntype UploadImageRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Data:\n\t//\t*UploadImageRequest_Info\n\t//\t*UploadImageRequest_ChunkData\n\tData isUploadImageRequest_Data `protobuf_oneof:\"data\"`\n}\n\nfunc (x *UploadImageRequest) Reset() {\n\t*x = UploadImageRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UploadImageRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UploadImageRequest) ProtoMessage() {}\n\nfunc (x *UploadImageRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UploadImageRequest.ProtoReflect.Descriptor instead.\nfunc (*UploadImageRequest) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (m *UploadImageRequest) GetData() isUploadImageRequest_Data {\n\tif m != nil {\n\t\treturn m.Data\n\t}\n\treturn nil\n}\n\nfunc (x *UploadImageRequest) GetInfo() *ImageInfo {\n\tif x, ok := x.GetData().(*UploadImageRequest_Info); ok {\n\t\treturn x.Info\n\t}\n\treturn nil\n}\n\nfunc (x *UploadImageRequest) GetChunkData() []byte {\n\tif x, ok := x.GetData().(*UploadImageRequest_ChunkData); ok {\n\t\treturn x.ChunkData\n\t}\n\treturn nil\n}\n\ntype isUploadImageRequest_Data interface {\n\tisUploadImageRequest_Data()\n}\n\ntype UploadImageRequest_Info struct {\n\tInfo *ImageInfo `protobuf:\"bytes,1,opt,name=info,proto3,oneof\"`\n}\n\ntype UploadImageRequest_ChunkData struct {\n\tChunkData []byte `protobuf:\"bytes,2,opt,name=chunk_data,json=chunkData,proto3,oneof\"`\n}\n\nfunc (*UploadImageRequest_Info) isUploadImageRequest_Data() {}\n\nfunc (*UploadImageRequest_ChunkData) isUploadImageRequest_Data() {}\n\ntype ImageInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLaptopId  string `protobuf:\"bytes,1,opt,name=laptop_id,json=laptopId,proto3\" json:\"laptop_id,omitempty\"`\n\tImageType string `protobuf:\"bytes,2,opt,name=image_type,json=imageType,proto3\" json:\"image_type,omitempty\"`\n}\n\nfunc (x *ImageInfo) Reset() {\n\t*x = ImageInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ImageInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ImageInfo) ProtoMessage() {}\n\nfunc (x *ImageInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ImageInfo.ProtoReflect.Descriptor instead.\nfunc (*ImageInfo) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ImageInfo) GetLaptopId() string {\n\tif x != nil {\n\t\treturn x.LaptopId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ImageInfo) GetImageType() string {\n\tif x != nil {\n\t\treturn x.ImageType\n\t}\n\treturn \"\"\n}\n\ntype UploadImageResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId   string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tSize uint32 `protobuf:\"varint,2,opt,name=size,proto3\" json:\"size,omitempty\"`\n}\n\nfunc (x *UploadImageResponse) Reset() {\n\t*x = UploadImageResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UploadImageResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UploadImageResponse) ProtoMessage() {}\n\nfunc (x *UploadImageResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UploadImageResponse.ProtoReflect.Descriptor instead.\nfunc (*UploadImageResponse) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *UploadImageResponse) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *UploadImageResponse) GetSize() uint32 {\n\tif x != nil {\n\t\treturn x.Size\n\t}\n\treturn 0\n}\n\ntype RateLaptopRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLaptopId string  `protobuf:\"bytes,1,opt,name=laptop_id,json=laptopId,proto3\" json:\"laptop_id,omitempty\"`\n\tScore    float64 `protobuf:\"fixed64,2,opt,name=score,proto3\" json:\"score,omitempty\"`\n}\n\nfunc (x *RateLaptopRequest) Reset() {\n\t*x = RateLaptopRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RateLaptopRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RateLaptopRequest) ProtoMessage() {}\n\nfunc (x *RateLaptopRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RateLaptopRequest.ProtoReflect.Descriptor instead.\nfunc (*RateLaptopRequest) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *RateLaptopRequest) GetLaptopId() string {\n\tif x != nil {\n\t\treturn x.LaptopId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RateLaptopRequest) GetScore() float64 {\n\tif x != nil {\n\t\treturn x.Score\n\t}\n\treturn 0\n}\n\ntype RateLaptopResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLaptopId     string  `protobuf:\"bytes,1,opt,name=laptop_id,json=laptopId,proto3\" json:\"laptop_id,omitempty\"`\n\tRatedCount   uint32  `protobuf:\"varint,2,opt,name=rated_count,json=ratedCount,proto3\" json:\"rated_count,omitempty\"`\n\tAverageScore float64 `protobuf:\"fixed64,3,opt,name=average_score,json=averageScore,proto3\" json:\"average_score,omitempty\"`\n}\n\nfunc (x *RateLaptopResponse) Reset() {\n\t*x = RateLaptopResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_laptop_service_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RateLaptopResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RateLaptopResponse) ProtoMessage() {}\n\nfunc (x *RateLaptopResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_laptop_service_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RateLaptopResponse.ProtoReflect.Descriptor instead.\nfunc (*RateLaptopResponse) Descriptor() ([]byte, []int) {\n\treturn file_laptop_service_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *RateLaptopResponse) GetLaptopId() string {\n\tif x != nil {\n\t\treturn x.LaptopId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RateLaptopResponse) GetRatedCount() uint32 {\n\tif x != nil {\n\t\treturn x.RatedCount\n\t}\n\treturn 0\n}\n\nfunc (x *RateLaptopResponse) GetAverageScore() float64 {\n\tif x != nil {\n\t\treturn x.AverageScore\n\t}\n\treturn 0\n}\n\nvar File_laptop_service_proto protoreflect.FileDescriptor\n\nvar file_laptop_service_proto_rawDesc = []byte{\n\t0x0a, 0x14, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6c, 0x61, 0x70, 0x74, 0x6f,\n\t0x70, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,\n\t0x14, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70,\n\t0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x22, 0x48, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x70,\n\t0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x6c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63,\n\t0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c,\n\t0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x06, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x22, 0x26, 0x0a,\n\t0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x48, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c,\n\t0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x06,\n\t0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74,\n\t0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b,\n\t0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22,\n\t0x49, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6c, 0x61, 0x70, 0x74, 0x6f,\n\t0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63,\n\t0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4c, 0x61, 0x70, 0x74,\n\t0x6f, 0x70, 0x52, 0x06, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x22, 0x71, 0x0a, 0x12, 0x55, 0x70,\n\t0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x32, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,\n\t0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f,\n\t0x6f, 0x6b, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x04,\n\t0x69, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x64, 0x61,\n\t0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e,\n\t0x6b, 0x44, 0x61, 0x74, 0x61, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x47, 0x0a,\n\t0x09, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c,\n\t0x61, 0x70, 0x74, 0x6f, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65,\n\t0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61,\n\t0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x39, 0x0a, 0x13, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,\n\t0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a,\n\t0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a,\n\t0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a,\n\t0x65, 0x22, 0x46, 0x0a, 0x11, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x70, 0x74, 0x6f,\n\t0x70, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x01, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x77, 0x0a, 0x12, 0x52, 0x61, 0x74,\n\t0x65, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,\n\t0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0d, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a,\n\t0x0d, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x53, 0x63, 0x6f,\n\t0x72, 0x65, 0x32, 0x8c, 0x04, 0x0a, 0x0d, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x53, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x12, 0x7d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x12, 0x26, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f,\n\t0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c,\n\t0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x74,\n\t0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b,\n\t0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f,\n\t0x76, 0x31, 0x2f, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,\n\t0x3a, 0x01, 0x2a, 0x12, 0x7c, 0x0a, 0x0c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x61, 0x70,\n\t0x74, 0x6f, 0x70, 0x12, 0x26, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x74, 0x65,\n\t0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e,\n\t0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76,\n\t0x31, 0x2f, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x30,\n\t0x01, 0x12, 0x82, 0x01, 0x0a, 0x0b, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6d, 0x61, 0x67,\n\t0x65, 0x12, 0x25, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70,\n\t0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6d, 0x61, 0x67,\n\t0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x55, 0x70, 0x6c,\n\t0x6f, 0x61, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67,\n\t0x65, 0x3a, 0x01, 0x2a, 0x28, 0x01, 0x12, 0x79, 0x0a, 0x0a, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x61,\n\t0x70, 0x74, 0x6f, 0x70, 0x12, 0x24, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f,\n\t0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x70,\n\t0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x74, 0x65, 0x63,\n\t0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x52,\n\t0x61, 0x74, 0x65, 0x4c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x6c,\n\t0x61, 0x70, 0x74, 0x6f, 0x70, 0x2f, 0x72, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x28, 0x01, 0x30,\n\t0x01, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e,\n\t0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f,\n\t0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_laptop_service_proto_rawDescOnce sync.Once\n\tfile_laptop_service_proto_rawDescData = file_laptop_service_proto_rawDesc\n)\n\nfunc file_laptop_service_proto_rawDescGZIP() []byte {\n\tfile_laptop_service_proto_rawDescOnce.Do(func() {\n\t\tfile_laptop_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_laptop_service_proto_rawDescData)\n\t})\n\treturn file_laptop_service_proto_rawDescData\n}\n\nvar file_laptop_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9)\nvar file_laptop_service_proto_goTypes = []interface{}{\n\t(*CreateLaptopRequest)(nil),  // 0: techschool.pcbook.CreateLaptopRequest\n\t(*CreateLaptopResponse)(nil), // 1: techschool.pcbook.CreateLaptopResponse\n\t(*SearchLaptopRequest)(nil),  // 2: techschool.pcbook.SearchLaptopRequest\n\t(*SearchLaptopResponse)(nil), // 3: techschool.pcbook.SearchLaptopResponse\n\t(*UploadImageRequest)(nil),   // 4: techschool.pcbook.UploadImageRequest\n\t(*ImageInfo)(nil),            // 5: techschool.pcbook.ImageInfo\n\t(*UploadImageResponse)(nil),  // 6: techschool.pcbook.UploadImageResponse\n\t(*RateLaptopRequest)(nil),    // 7: techschool.pcbook.RateLaptopRequest\n\t(*RateLaptopResponse)(nil),   // 8: techschool.pcbook.RateLaptopResponse\n\t(*Laptop)(nil),               // 9: techschool.pcbook.Laptop\n\t(*Filter)(nil),               // 10: techschool.pcbook.Filter\n}\nvar file_laptop_service_proto_depIdxs = []int32{\n\t9,  // 0: techschool.pcbook.CreateLaptopRequest.laptop:type_name -> techschool.pcbook.Laptop\n\t10, // 1: techschool.pcbook.SearchLaptopRequest.filter:type_name -> techschool.pcbook.Filter\n\t9,  // 2: techschool.pcbook.SearchLaptopResponse.laptop:type_name -> techschool.pcbook.Laptop\n\t5,  // 3: techschool.pcbook.UploadImageRequest.info:type_name -> techschool.pcbook.ImageInfo\n\t0,  // 4: techschool.pcbook.LaptopService.CreateLaptop:input_type -> techschool.pcbook.CreateLaptopRequest\n\t2,  // 5: techschool.pcbook.LaptopService.SearchLaptop:input_type -> techschool.pcbook.SearchLaptopRequest\n\t4,  // 6: techschool.pcbook.LaptopService.UploadImage:input_type -> techschool.pcbook.UploadImageRequest\n\t7,  // 7: techschool.pcbook.LaptopService.RateLaptop:input_type -> techschool.pcbook.RateLaptopRequest\n\t1,  // 8: techschool.pcbook.LaptopService.CreateLaptop:output_type -> techschool.pcbook.CreateLaptopResponse\n\t3,  // 9: techschool.pcbook.LaptopService.SearchLaptop:output_type -> techschool.pcbook.SearchLaptopResponse\n\t6,  // 10: techschool.pcbook.LaptopService.UploadImage:output_type -> techschool.pcbook.UploadImageResponse\n\t8,  // 11: techschool.pcbook.LaptopService.RateLaptop:output_type -> techschool.pcbook.RateLaptopResponse\n\t8,  // [8:12] is the sub-list for method output_type\n\t4,  // [4:8] is the sub-list for method input_type\n\t4,  // [4:4] is the sub-list for extension type_name\n\t4,  // [4:4] is the sub-list for extension extendee\n\t0,  // [0:4] is the sub-list for field type_name\n}\n\nfunc init() { file_laptop_service_proto_init() }\nfunc file_laptop_service_proto_init() {\n\tif File_laptop_service_proto != nil {\n\t\treturn\n\t}\n\tfile_laptop_message_proto_init()\n\tfile_filter_message_proto_init()\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_laptop_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateLaptopRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CreateLaptopResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SearchLaptopRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SearchLaptopResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UploadImageRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ImageInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UploadImageResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*RateLaptopRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_laptop_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*RateLaptopResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tfile_laptop_service_proto_msgTypes[4].OneofWrappers = []interface{}{\n\t\t(*UploadImageRequest_Info)(nil),\n\t\t(*UploadImageRequest_ChunkData)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_laptop_service_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   9,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_laptop_service_proto_goTypes,\n\t\tDependencyIndexes: file_laptop_service_proto_depIdxs,\n\t\tMessageInfos:      file_laptop_service_proto_msgTypes,\n\t}.Build()\n\tFile_laptop_service_proto = out.File\n\tfile_laptop_service_proto_rawDesc = nil\n\tfile_laptop_service_proto_goTypes = nil\n\tfile_laptop_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/laptop_service.pb.gw.go",
    "content": "// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.\n// source: laptop_service.proto\n\n/*\nPackage pb is a reverse proxy.\n\nIt translates gRPC into RESTful JSON APIs.\n*/\npackage pb\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/runtime\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/utilities\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/grpclog\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// Suppress \"imported and not used\" errors\nvar _ codes.Code\nvar _ io.Reader\nvar _ status.Status\nvar _ = runtime.String\nvar _ = utilities.NewDoubleArray\nvar _ = metadata.Join\n\nfunc request_LaptopService_CreateLaptop_0(ctx context.Context, marshaler runtime.Marshaler, client LaptopServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateLaptopRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := client.CreateLaptop(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))\n\treturn msg, metadata, err\n\n}\n\nfunc local_request_LaptopService_CreateLaptop_0(ctx context.Context, marshaler runtime.Marshaler, server LaptopServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar protoReq CreateLaptopRequest\n\tvar metadata runtime.ServerMetadata\n\n\tnewReader, berr := utilities.IOReaderFactory(req.Body)\n\tif berr != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", berr)\n\t}\n\tif err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tmsg, err := server.CreateLaptop(ctx, &protoReq)\n\treturn msg, metadata, err\n\n}\n\nvar (\n\tfilter_LaptopService_SearchLaptop_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}\n)\n\nfunc request_LaptopService_SearchLaptop_0(ctx context.Context, marshaler runtime.Marshaler, client LaptopServiceClient, req *http.Request, pathParams map[string]string) (LaptopService_SearchLaptopClient, runtime.ServerMetadata, error) {\n\tvar protoReq SearchLaptopRequest\n\tvar metadata runtime.ServerMetadata\n\n\tif err := req.ParseForm(); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\tif err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LaptopService_SearchLaptop_0); err != nil {\n\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t}\n\n\tstream, err := client.SearchLaptop(ctx, &protoReq)\n\tif err != nil {\n\t\treturn nil, metadata, err\n\t}\n\theader, err := stream.Header()\n\tif err != nil {\n\t\treturn nil, metadata, err\n\t}\n\tmetadata.HeaderMD = header\n\treturn stream, metadata, nil\n\n}\n\nfunc request_LaptopService_UploadImage_0(ctx context.Context, marshaler runtime.Marshaler, client LaptopServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {\n\tvar metadata runtime.ServerMetadata\n\tstream, err := client.UploadImage(ctx)\n\tif err != nil {\n\t\tgrpclog.Infof(\"Failed to start streaming: %v\", err)\n\t\treturn nil, metadata, err\n\t}\n\tdec := marshaler.NewDecoder(req.Body)\n\tfor {\n\t\tvar protoReq UploadImageRequest\n\t\terr = dec.Decode(&protoReq)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tgrpclog.Infof(\"Failed to decode request: %v\", err)\n\t\t\treturn nil, metadata, status.Errorf(codes.InvalidArgument, \"%v\", err)\n\t\t}\n\t\tif err = stream.Send(&protoReq); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tgrpclog.Infof(\"Failed to send request: %v\", err)\n\t\t\treturn nil, metadata, err\n\t\t}\n\t}\n\n\tif err := stream.CloseSend(); err != nil {\n\t\tgrpclog.Infof(\"Failed to terminate client stream: %v\", err)\n\t\treturn nil, metadata, err\n\t}\n\theader, err := stream.Header()\n\tif err != nil {\n\t\tgrpclog.Infof(\"Failed to get header from client: %v\", err)\n\t\treturn nil, metadata, err\n\t}\n\tmetadata.HeaderMD = header\n\n\tmsg, err := stream.CloseAndRecv()\n\tmetadata.TrailerMD = stream.Trailer()\n\treturn msg, metadata, err\n\n}\n\nfunc request_LaptopService_RateLaptop_0(ctx context.Context, marshaler runtime.Marshaler, client LaptopServiceClient, req *http.Request, pathParams map[string]string) (LaptopService_RateLaptopClient, runtime.ServerMetadata, error) {\n\tvar metadata runtime.ServerMetadata\n\tstream, err := client.RateLaptop(ctx)\n\tif err != nil {\n\t\tgrpclog.Infof(\"Failed to start streaming: %v\", err)\n\t\treturn nil, metadata, err\n\t}\n\tdec := marshaler.NewDecoder(req.Body)\n\thandleSend := func() error {\n\t\tvar protoReq RateLaptopRequest\n\t\terr := dec.Decode(&protoReq)\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\t\tif err != nil {\n\t\t\tgrpclog.Infof(\"Failed to decode request: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tif err := stream.Send(&protoReq); err != nil {\n\t\t\tgrpclog.Infof(\"Failed to send request: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\tif err := handleSend(); err != nil {\n\t\tif cerr := stream.CloseSend(); cerr != nil {\n\t\t\tgrpclog.Infof(\"Failed to terminate client stream: %v\", cerr)\n\t\t}\n\t\tif err == io.EOF {\n\t\t\treturn stream, metadata, nil\n\t\t}\n\t\treturn nil, metadata, err\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tif err := handleSend(); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif err := stream.CloseSend(); err != nil {\n\t\t\tgrpclog.Infof(\"Failed to terminate client stream: %v\", err)\n\t\t}\n\t}()\n\theader, err := stream.Header()\n\tif err != nil {\n\t\tgrpclog.Infof(\"Failed to get header from client: %v\", err)\n\t\treturn nil, metadata, err\n\t}\n\tmetadata.HeaderMD = header\n\treturn stream, metadata, nil\n}\n\n// RegisterLaptopServiceHandlerServer registers the http handlers for service LaptopService to \"mux\".\n// UnaryRPC     :call LaptopServiceServer directly.\n// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.\n// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterLaptopServiceHandlerFromEndpoint instead.\nfunc RegisterLaptopServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LaptopServiceServer) error {\n\n\tmux.Handle(\"POST\", pattern_LaptopService_CreateLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tvar stream runtime.ServerTransportStream\n\t\tctx = grpc.NewContextWithServerTransportStream(ctx, &stream)\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, \"/techschool.pcbook.LaptopService/CreateLaptop\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := local_request_LaptopService_CreateLaptop_0(rctx, inboundMarshaler, server, req, pathParams)\n\t\tmd.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_LaptopService_CreateLaptop_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_LaptopService_SearchLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\terr := status.Error(codes.Unimplemented, \"streaming calls are not yet supported in the in-process transport\")\n\t\t_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\treturn\n\t})\n\n\tmux.Handle(\"POST\", pattern_LaptopService_UploadImage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\terr := status.Error(codes.Unimplemented, \"streaming calls are not yet supported in the in-process transport\")\n\t\t_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\treturn\n\t})\n\n\tmux.Handle(\"POST\", pattern_LaptopService_RateLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\terr := status.Error(codes.Unimplemented, \"streaming calls are not yet supported in the in-process transport\")\n\t\t_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\treturn\n\t})\n\n\treturn nil\n}\n\n// RegisterLaptopServiceHandlerFromEndpoint is same as RegisterLaptopServiceHandler but\n// automatically dials to \"endpoint\" and closes the connection when \"ctx\" gets done.\nfunc RegisterLaptopServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {\n\tconn, err := grpc.Dial(endpoint, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\t\t\tif cerr := conn.Close(); cerr != nil {\n\t\t\t\tgrpclog.Infof(\"Failed to close conn to %s: %v\", endpoint, cerr)\n\t\t\t}\n\t\t}()\n\t}()\n\n\treturn RegisterLaptopServiceHandler(ctx, mux, conn)\n}\n\n// RegisterLaptopServiceHandler registers the http handlers for service LaptopService to \"mux\".\n// The handlers forward requests to the grpc endpoint over \"conn\".\nfunc RegisterLaptopServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {\n\treturn RegisterLaptopServiceHandlerClient(ctx, mux, NewLaptopServiceClient(conn))\n}\n\n// RegisterLaptopServiceHandlerClient registers the http handlers for service LaptopService\n// to \"mux\". The handlers forward requests to the grpc endpoint over the given implementation of \"LaptopServiceClient\".\n// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in \"LaptopServiceClient\"\n// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in\n// \"LaptopServiceClient\" to call the correct interceptors.\nfunc RegisterLaptopServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LaptopServiceClient) error {\n\n\tmux.Handle(\"POST\", pattern_LaptopService_CreateLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req, \"/techschool.pcbook.LaptopService/CreateLaptop\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_LaptopService_CreateLaptop_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_LaptopService_CreateLaptop_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"GET\", pattern_LaptopService_SearchLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req, \"/techschool.pcbook.LaptopService/SearchLaptop\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_LaptopService_SearchLaptop_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_LaptopService_SearchLaptop_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_LaptopService_UploadImage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req, \"/techschool.pcbook.LaptopService/UploadImage\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_LaptopService_UploadImage_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_LaptopService_UploadImage_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\tmux.Handle(\"POST\", pattern_LaptopService_RateLaptop_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {\n\t\tctx, cancel := context.WithCancel(req.Context())\n\t\tdefer cancel()\n\t\tinboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)\n\t\trctx, err := runtime.AnnotateContext(ctx, mux, req, \"/techschool.pcbook.LaptopService/RateLaptop\")\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\t\tresp, md, err := request_LaptopService_RateLaptop_0(rctx, inboundMarshaler, client, req, pathParams)\n\t\tctx = runtime.NewServerMetadataContext(ctx, md)\n\t\tif err != nil {\n\t\t\truntime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)\n\t\t\treturn\n\t\t}\n\n\t\tforward_LaptopService_RateLaptop_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)\n\n\t})\n\n\treturn nil\n}\n\nvar (\n\tpattern_LaptopService_CreateLaptop_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"v1\", \"laptop\", \"create\"}, \"\"))\n\n\tpattern_LaptopService_SearchLaptop_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"v1\", \"laptop\", \"search\"}, \"\"))\n\n\tpattern_LaptopService_UploadImage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"v1\", \"laptop\", \"upload_image\"}, \"\"))\n\n\tpattern_LaptopService_RateLaptop_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{\"v1\", \"laptop\", \"rate\"}, \"\"))\n)\n\nvar (\n\tforward_LaptopService_CreateLaptop_0 = runtime.ForwardResponseMessage\n\n\tforward_LaptopService_SearchLaptop_0 = runtime.ForwardResponseStream\n\n\tforward_LaptopService_UploadImage_0 = runtime.ForwardResponseMessage\n\n\tforward_LaptopService_RateLaptop_0 = runtime.ForwardResponseStream\n)\n"
  },
  {
    "path": "pb/laptop_service_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage pb\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// LaptopServiceClient is the client API for LaptopService service.\n//\n// 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.\ntype LaptopServiceClient interface {\n\tCreateLaptop(ctx context.Context, in *CreateLaptopRequest, opts ...grpc.CallOption) (*CreateLaptopResponse, error)\n\tSearchLaptop(ctx context.Context, in *SearchLaptopRequest, opts ...grpc.CallOption) (LaptopService_SearchLaptopClient, error)\n\tUploadImage(ctx context.Context, opts ...grpc.CallOption) (LaptopService_UploadImageClient, error)\n\tRateLaptop(ctx context.Context, opts ...grpc.CallOption) (LaptopService_RateLaptopClient, error)\n}\n\ntype laptopServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewLaptopServiceClient(cc grpc.ClientConnInterface) LaptopServiceClient {\n\treturn &laptopServiceClient{cc}\n}\n\nfunc (c *laptopServiceClient) CreateLaptop(ctx context.Context, in *CreateLaptopRequest, opts ...grpc.CallOption) (*CreateLaptopResponse, error) {\n\tout := new(CreateLaptopResponse)\n\terr := c.cc.Invoke(ctx, \"/techschool.pcbook.LaptopService/CreateLaptop\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *laptopServiceClient) SearchLaptop(ctx context.Context, in *SearchLaptopRequest, opts ...grpc.CallOption) (LaptopService_SearchLaptopClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[0], \"/techschool.pcbook.LaptopService/SearchLaptop\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &laptopServiceSearchLaptopClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype LaptopService_SearchLaptopClient interface {\n\tRecv() (*SearchLaptopResponse, error)\n\tgrpc.ClientStream\n}\n\ntype laptopServiceSearchLaptopClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *laptopServiceSearchLaptopClient) Recv() (*SearchLaptopResponse, error) {\n\tm := new(SearchLaptopResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *laptopServiceClient) UploadImage(ctx context.Context, opts ...grpc.CallOption) (LaptopService_UploadImageClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[1], \"/techschool.pcbook.LaptopService/UploadImage\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &laptopServiceUploadImageClient{stream}\n\treturn x, nil\n}\n\ntype LaptopService_UploadImageClient interface {\n\tSend(*UploadImageRequest) error\n\tCloseAndRecv() (*UploadImageResponse, error)\n\tgrpc.ClientStream\n}\n\ntype laptopServiceUploadImageClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *laptopServiceUploadImageClient) Send(m *UploadImageRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *laptopServiceUploadImageClient) CloseAndRecv() (*UploadImageResponse, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(UploadImageResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *laptopServiceClient) RateLaptop(ctx context.Context, opts ...grpc.CallOption) (LaptopService_RateLaptopClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &LaptopService_ServiceDesc.Streams[2], \"/techschool.pcbook.LaptopService/RateLaptop\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &laptopServiceRateLaptopClient{stream}\n\treturn x, nil\n}\n\ntype LaptopService_RateLaptopClient interface {\n\tSend(*RateLaptopRequest) error\n\tRecv() (*RateLaptopResponse, error)\n\tgrpc.ClientStream\n}\n\ntype laptopServiceRateLaptopClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *laptopServiceRateLaptopClient) Send(m *RateLaptopRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *laptopServiceRateLaptopClient) Recv() (*RateLaptopResponse, error) {\n\tm := new(RateLaptopResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// LaptopServiceServer is the server API for LaptopService service.\n// All implementations must embed UnimplementedLaptopServiceServer\n// for forward compatibility\ntype LaptopServiceServer interface {\n\tCreateLaptop(context.Context, *CreateLaptopRequest) (*CreateLaptopResponse, error)\n\tSearchLaptop(*SearchLaptopRequest, LaptopService_SearchLaptopServer) error\n\tUploadImage(LaptopService_UploadImageServer) error\n\tRateLaptop(LaptopService_RateLaptopServer) error\n\tmustEmbedUnimplementedLaptopServiceServer()\n}\n\n// UnimplementedLaptopServiceServer must be embedded to have forward compatible implementations.\ntype UnimplementedLaptopServiceServer struct {\n}\n\nfunc (UnimplementedLaptopServiceServer) CreateLaptop(context.Context, *CreateLaptopRequest) (*CreateLaptopResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateLaptop not implemented\")\n}\nfunc (UnimplementedLaptopServiceServer) SearchLaptop(*SearchLaptopRequest, LaptopService_SearchLaptopServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method SearchLaptop not implemented\")\n}\nfunc (UnimplementedLaptopServiceServer) UploadImage(LaptopService_UploadImageServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method UploadImage not implemented\")\n}\nfunc (UnimplementedLaptopServiceServer) RateLaptop(LaptopService_RateLaptopServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method RateLaptop not implemented\")\n}\nfunc (UnimplementedLaptopServiceServer) mustEmbedUnimplementedLaptopServiceServer() {}\n\n// UnsafeLaptopServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to LaptopServiceServer will\n// result in compilation errors.\ntype UnsafeLaptopServiceServer interface {\n\tmustEmbedUnimplementedLaptopServiceServer()\n}\n\nfunc RegisterLaptopServiceServer(s grpc.ServiceRegistrar, srv LaptopServiceServer) {\n\ts.RegisterService(&LaptopService_ServiceDesc, srv)\n}\n\nfunc _LaptopService_CreateLaptop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateLaptopRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LaptopServiceServer).CreateLaptop(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/techschool.pcbook.LaptopService/CreateLaptop\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LaptopServiceServer).CreateLaptop(ctx, req.(*CreateLaptopRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LaptopService_SearchLaptop_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SearchLaptopRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(LaptopServiceServer).SearchLaptop(m, &laptopServiceSearchLaptopServer{stream})\n}\n\ntype LaptopService_SearchLaptopServer interface {\n\tSend(*SearchLaptopResponse) error\n\tgrpc.ServerStream\n}\n\ntype laptopServiceSearchLaptopServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *laptopServiceSearchLaptopServer) Send(m *SearchLaptopResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _LaptopService_UploadImage_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(LaptopServiceServer).UploadImage(&laptopServiceUploadImageServer{stream})\n}\n\ntype LaptopService_UploadImageServer interface {\n\tSendAndClose(*UploadImageResponse) error\n\tRecv() (*UploadImageRequest, error)\n\tgrpc.ServerStream\n}\n\ntype laptopServiceUploadImageServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *laptopServiceUploadImageServer) SendAndClose(m *UploadImageResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *laptopServiceUploadImageServer) Recv() (*UploadImageRequest, error) {\n\tm := new(UploadImageRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _LaptopService_RateLaptop_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(LaptopServiceServer).RateLaptop(&laptopServiceRateLaptopServer{stream})\n}\n\ntype LaptopService_RateLaptopServer interface {\n\tSend(*RateLaptopResponse) error\n\tRecv() (*RateLaptopRequest, error)\n\tgrpc.ServerStream\n}\n\ntype laptopServiceRateLaptopServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *laptopServiceRateLaptopServer) Send(m *RateLaptopResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *laptopServiceRateLaptopServer) Recv() (*RateLaptopRequest, error) {\n\tm := new(RateLaptopRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// LaptopService_ServiceDesc is the grpc.ServiceDesc for LaptopService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar LaptopService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"techschool.pcbook.LaptopService\",\n\tHandlerType: (*LaptopServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"CreateLaptop\",\n\t\t\tHandler:    _LaptopService_CreateLaptop_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"SearchLaptop\",\n\t\t\tHandler:       _LaptopService_SearchLaptop_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"UploadImage\",\n\t\t\tHandler:       _LaptopService_UploadImage_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"RateLaptop\",\n\t\t\tHandler:       _LaptopService_RateLaptop_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"laptop_service.proto\",\n}\n"
  },
  {
    "path": "pb/memory_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: memory_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Memory_Unit int32\n\nconst (\n\tMemory_UNKNOWN  Memory_Unit = 0\n\tMemory_BIT      Memory_Unit = 1\n\tMemory_BYTE     Memory_Unit = 2\n\tMemory_KILOBYTE Memory_Unit = 3\n\tMemory_MEGABYTE Memory_Unit = 4\n\tMemory_GIGABYTE Memory_Unit = 5\n\tMemory_TERABYTE Memory_Unit = 6\n)\n\n// Enum value maps for Memory_Unit.\nvar (\n\tMemory_Unit_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"BIT\",\n\t\t2: \"BYTE\",\n\t\t3: \"KILOBYTE\",\n\t\t4: \"MEGABYTE\",\n\t\t5: \"GIGABYTE\",\n\t\t6: \"TERABYTE\",\n\t}\n\tMemory_Unit_value = map[string]int32{\n\t\t\"UNKNOWN\":  0,\n\t\t\"BIT\":      1,\n\t\t\"BYTE\":     2,\n\t\t\"KILOBYTE\": 3,\n\t\t\"MEGABYTE\": 4,\n\t\t\"GIGABYTE\": 5,\n\t\t\"TERABYTE\": 6,\n\t}\n)\n\nfunc (x Memory_Unit) Enum() *Memory_Unit {\n\tp := new(Memory_Unit)\n\t*p = x\n\treturn p\n}\n\nfunc (x Memory_Unit) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Memory_Unit) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_memory_message_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Memory_Unit) Type() protoreflect.EnumType {\n\treturn &file_memory_message_proto_enumTypes[0]\n}\n\nfunc (x Memory_Unit) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Memory_Unit.Descriptor instead.\nfunc (Memory_Unit) EnumDescriptor() ([]byte, []int) {\n\treturn file_memory_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Memory struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tValue uint64      `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tUnit  Memory_Unit `protobuf:\"varint,2,opt,name=unit,proto3,enum=techschool.pcbook.Memory_Unit\" json:\"unit,omitempty\"`\n}\n\nfunc (x *Memory) Reset() {\n\t*x = Memory{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_memory_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Memory) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Memory) ProtoMessage() {}\n\nfunc (x *Memory) ProtoReflect() protoreflect.Message {\n\tmi := &file_memory_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Memory.ProtoReflect.Descriptor instead.\nfunc (*Memory) Descriptor() ([]byte, []int) {\n\treturn file_memory_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Memory) GetValue() uint64 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\nfunc (x *Memory) GetUnit() Memory_Unit {\n\tif x != nil {\n\t\treturn x.Unit\n\t}\n\treturn Memory_UNKNOWN\n}\n\nvar File_memory_message_proto protoreflect.FileDescriptor\n\nvar file_memory_message_proto_rawDesc = []byte{\n\t0x0a, 0x14, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0xb2, 0x01, 0x0a, 0x06, 0x4d, 0x65,\n\t0x6d, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x75, 0x6e,\n\t0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d,\n\t0x6f, 0x72, 0x79, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x22, 0x5e,\n\t0x0a, 0x04, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,\n\t0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x42, 0x49, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04,\n\t0x42, 0x59, 0x54, 0x45, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4b, 0x49, 0x4c, 0x4f, 0x42, 0x59,\n\t0x54, 0x45, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x45, 0x47, 0x41, 0x42, 0x59, 0x54, 0x45,\n\t0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x47, 0x49, 0x47, 0x41, 0x42, 0x59, 0x54, 0x45, 0x10, 0x05,\n\t0x12, 0x0c, 0x0a, 0x08, 0x54, 0x45, 0x52, 0x41, 0x42, 0x59, 0x54, 0x45, 0x10, 0x06, 0x42, 0x29,\n\t0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63,\n\t0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70,\n\t0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x33,\n}\n\nvar (\n\tfile_memory_message_proto_rawDescOnce sync.Once\n\tfile_memory_message_proto_rawDescData = file_memory_message_proto_rawDesc\n)\n\nfunc file_memory_message_proto_rawDescGZIP() []byte {\n\tfile_memory_message_proto_rawDescOnce.Do(func() {\n\t\tfile_memory_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_memory_message_proto_rawDescData)\n\t})\n\treturn file_memory_message_proto_rawDescData\n}\n\nvar file_memory_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_memory_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_memory_message_proto_goTypes = []interface{}{\n\t(Memory_Unit)(0), // 0: techschool.pcbook.Memory.Unit\n\t(*Memory)(nil),   // 1: techschool.pcbook.Memory\n}\nvar file_memory_message_proto_depIdxs = []int32{\n\t0, // 0: techschool.pcbook.Memory.unit:type_name -> techschool.pcbook.Memory.Unit\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_memory_message_proto_init() }\nfunc file_memory_message_proto_init() {\n\tif File_memory_message_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_memory_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Memory); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_memory_message_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_memory_message_proto_goTypes,\n\t\tDependencyIndexes: file_memory_message_proto_depIdxs,\n\t\tEnumInfos:         file_memory_message_proto_enumTypes,\n\t\tMessageInfos:      file_memory_message_proto_msgTypes,\n\t}.Build()\n\tFile_memory_message_proto = out.File\n\tfile_memory_message_proto_rawDesc = nil\n\tfile_memory_message_proto_goTypes = nil\n\tfile_memory_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/processor_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: processor_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype CPU struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tBrand         string  `protobuf:\"bytes,1,opt,name=brand,proto3\" json:\"brand,omitempty\"`\n\tName          string  `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tNumberCores   uint32  `protobuf:\"varint,3,opt,name=number_cores,json=numberCores,proto3\" json:\"number_cores,omitempty\"`\n\tNumberThreads uint32  `protobuf:\"varint,4,opt,name=number_threads,json=numberThreads,proto3\" json:\"number_threads,omitempty\"`\n\tMinGhz        float64 `protobuf:\"fixed64,5,opt,name=min_ghz,json=minGhz,proto3\" json:\"min_ghz,omitempty\"`\n\tMaxGhz        float64 `protobuf:\"fixed64,6,opt,name=max_ghz,json=maxGhz,proto3\" json:\"max_ghz,omitempty\"`\n}\n\nfunc (x *CPU) Reset() {\n\t*x = CPU{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_processor_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CPU) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CPU) ProtoMessage() {}\n\nfunc (x *CPU) ProtoReflect() protoreflect.Message {\n\tmi := &file_processor_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CPU.ProtoReflect.Descriptor instead.\nfunc (*CPU) Descriptor() ([]byte, []int) {\n\treturn file_processor_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CPU) GetBrand() string {\n\tif x != nil {\n\t\treturn x.Brand\n\t}\n\treturn \"\"\n}\n\nfunc (x *CPU) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *CPU) GetNumberCores() uint32 {\n\tif x != nil {\n\t\treturn x.NumberCores\n\t}\n\treturn 0\n}\n\nfunc (x *CPU) GetNumberThreads() uint32 {\n\tif x != nil {\n\t\treturn x.NumberThreads\n\t}\n\treturn 0\n}\n\nfunc (x *CPU) GetMinGhz() float64 {\n\tif x != nil {\n\t\treturn x.MinGhz\n\t}\n\treturn 0\n}\n\nfunc (x *CPU) GetMaxGhz() float64 {\n\tif x != nil {\n\t\treturn x.MaxGhz\n\t}\n\treturn 0\n}\n\ntype GPU struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tBrand  string  `protobuf:\"bytes,1,opt,name=brand,proto3\" json:\"brand,omitempty\"`\n\tName   string  `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tMinGhz float64 `protobuf:\"fixed64,3,opt,name=min_ghz,json=minGhz,proto3\" json:\"min_ghz,omitempty\"`\n\tMaxGhz float64 `protobuf:\"fixed64,4,opt,name=max_ghz,json=maxGhz,proto3\" json:\"max_ghz,omitempty\"`\n\tMemory *Memory `protobuf:\"bytes,5,opt,name=memory,proto3\" json:\"memory,omitempty\"`\n}\n\nfunc (x *GPU) Reset() {\n\t*x = GPU{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_processor_message_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GPU) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GPU) ProtoMessage() {}\n\nfunc (x *GPU) ProtoReflect() protoreflect.Message {\n\tmi := &file_processor_message_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GPU.ProtoReflect.Descriptor instead.\nfunc (*GPU) Descriptor() ([]byte, []int) {\n\treturn file_processor_message_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *GPU) GetBrand() string {\n\tif x != nil {\n\t\treturn x.Brand\n\t}\n\treturn \"\"\n}\n\nfunc (x *GPU) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *GPU) GetMinGhz() float64 {\n\tif x != nil {\n\t\treturn x.MinGhz\n\t}\n\treturn 0\n}\n\nfunc (x *GPU) GetMaxGhz() float64 {\n\tif x != nil {\n\t\treturn x.MaxGhz\n\t}\n\treturn 0\n}\n\nfunc (x *GPU) GetMemory() *Memory {\n\tif x != nil {\n\t\treturn x.Memory\n\t}\n\treturn nil\n}\n\nvar File_processor_message_proto protoreflect.FileDescriptor\n\nvar file_processor_message_proto_rawDesc = []byte{\n\t0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73,\n\t0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73,\n\t0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65,\n\t0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x22, 0xab, 0x01, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72,\n\t0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64,\n\t0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x63,\n\t0x6f, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x62,\n\t0x65, 0x72, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x62, 0x65,\n\t0x72, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x0d, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x12, 0x17,\n\t0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52,\n\t0x06, 0x6d, 0x69, 0x6e, 0x47, 0x68, 0x7a, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x67,\n\t0x68, 0x7a, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x47, 0x68, 0x7a,\n\t0x22, 0x94, 0x01, 0x0a, 0x03, 0x47, 0x50, 0x55, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 0x61, 0x6e,\n\t0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x12, 0x12,\n\t0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x69, 0x6e, 0x47, 0x68, 0x7a, 0x12, 0x17, 0x0a, 0x07, 0x6d,\n\t0x61, 0x78, 0x5f, 0x67, 0x68, 0x7a, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x6d, 0x61,\n\t0x78, 0x47, 0x68, 0x7a, 0x12, 0x31, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f,\n\t0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52,\n\t0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67,\n\t0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c,\n\t0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b,\n\t0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_processor_message_proto_rawDescOnce sync.Once\n\tfile_processor_message_proto_rawDescData = file_processor_message_proto_rawDesc\n)\n\nfunc file_processor_message_proto_rawDescGZIP() []byte {\n\tfile_processor_message_proto_rawDescOnce.Do(func() {\n\t\tfile_processor_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_processor_message_proto_rawDescData)\n\t})\n\treturn file_processor_message_proto_rawDescData\n}\n\nvar file_processor_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_processor_message_proto_goTypes = []interface{}{\n\t(*CPU)(nil),    // 0: techschool.pcbook.CPU\n\t(*GPU)(nil),    // 1: techschool.pcbook.GPU\n\t(*Memory)(nil), // 2: techschool.pcbook.Memory\n}\nvar file_processor_message_proto_depIdxs = []int32{\n\t2, // 0: techschool.pcbook.GPU.memory:type_name -> techschool.pcbook.Memory\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_processor_message_proto_init() }\nfunc file_processor_message_proto_init() {\n\tif File_processor_message_proto != nil {\n\t\treturn\n\t}\n\tfile_memory_message_proto_init()\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_processor_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CPU); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_processor_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GPU); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_processor_message_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_processor_message_proto_goTypes,\n\t\tDependencyIndexes: file_processor_message_proto_depIdxs,\n\t\tMessageInfos:      file_processor_message_proto_msgTypes,\n\t}.Build()\n\tFile_processor_message_proto = out.File\n\tfile_processor_message_proto_rawDesc = nil\n\tfile_processor_message_proto_goTypes = nil\n\tfile_processor_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/screen_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: screen_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Screen_Panel int32\n\nconst (\n\tScreen_UNKNOWN Screen_Panel = 0\n\tScreen_IPS     Screen_Panel = 1\n\tScreen_OLED    Screen_Panel = 2\n)\n\n// Enum value maps for Screen_Panel.\nvar (\n\tScreen_Panel_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"IPS\",\n\t\t2: \"OLED\",\n\t}\n\tScreen_Panel_value = map[string]int32{\n\t\t\"UNKNOWN\": 0,\n\t\t\"IPS\":     1,\n\t\t\"OLED\":    2,\n\t}\n)\n\nfunc (x Screen_Panel) Enum() *Screen_Panel {\n\tp := new(Screen_Panel)\n\t*p = x\n\treturn p\n}\n\nfunc (x Screen_Panel) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Screen_Panel) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_screen_message_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Screen_Panel) Type() protoreflect.EnumType {\n\treturn &file_screen_message_proto_enumTypes[0]\n}\n\nfunc (x Screen_Panel) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Screen_Panel.Descriptor instead.\nfunc (Screen_Panel) EnumDescriptor() ([]byte, []int) {\n\treturn file_screen_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Screen struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSizeInch   float32            `protobuf:\"fixed32,1,opt,name=size_inch,json=sizeInch,proto3\" json:\"size_inch,omitempty\"`\n\tResolution *Screen_Resolution `protobuf:\"bytes,2,opt,name=resolution,proto3\" json:\"resolution,omitempty\"`\n\tPanel      Screen_Panel       `protobuf:\"varint,3,opt,name=panel,proto3,enum=techschool.pcbook.Screen_Panel\" json:\"panel,omitempty\"`\n\tMultitouch bool               `protobuf:\"varint,4,opt,name=multitouch,proto3\" json:\"multitouch,omitempty\"`\n}\n\nfunc (x *Screen) Reset() {\n\t*x = Screen{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_screen_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Screen) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Screen) ProtoMessage() {}\n\nfunc (x *Screen) ProtoReflect() protoreflect.Message {\n\tmi := &file_screen_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Screen.ProtoReflect.Descriptor instead.\nfunc (*Screen) Descriptor() ([]byte, []int) {\n\treturn file_screen_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Screen) GetSizeInch() float32 {\n\tif x != nil {\n\t\treturn x.SizeInch\n\t}\n\treturn 0\n}\n\nfunc (x *Screen) GetResolution() *Screen_Resolution {\n\tif x != nil {\n\t\treturn x.Resolution\n\t}\n\treturn nil\n}\n\nfunc (x *Screen) GetPanel() Screen_Panel {\n\tif x != nil {\n\t\treturn x.Panel\n\t}\n\treturn Screen_UNKNOWN\n}\n\nfunc (x *Screen) GetMultitouch() bool {\n\tif x != nil {\n\t\treturn x.Multitouch\n\t}\n\treturn false\n}\n\ntype Screen_Resolution struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tWidth  uint32 `protobuf:\"varint,1,opt,name=width,proto3\" json:\"width,omitempty\"`\n\tHeight uint32 `protobuf:\"varint,2,opt,name=height,proto3\" json:\"height,omitempty\"`\n}\n\nfunc (x *Screen_Resolution) Reset() {\n\t*x = Screen_Resolution{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_screen_message_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Screen_Resolution) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Screen_Resolution) ProtoMessage() {}\n\nfunc (x *Screen_Resolution) ProtoReflect() protoreflect.Message {\n\tmi := &file_screen_message_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Screen_Resolution.ProtoReflect.Descriptor instead.\nfunc (*Screen_Resolution) Descriptor() ([]byte, []int) {\n\treturn file_screen_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *Screen_Resolution) GetWidth() uint32 {\n\tif x != nil {\n\t\treturn x.Width\n\t}\n\treturn 0\n}\n\nfunc (x *Screen_Resolution) GetHeight() uint32 {\n\tif x != nil {\n\t\treturn x.Height\n\t}\n\treturn 0\n}\n\nvar File_screen_message_proto protoreflect.FileDescriptor\n\nvar file_screen_message_proto_rawDesc = []byte{\n\t0x0a, 0x14, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0xa7, 0x02, 0x0a, 0x06, 0x53, 0x63,\n\t0x72, 0x65, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x69, 0x6e, 0x63,\n\t0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x73, 0x69, 0x7a, 0x65, 0x49, 0x6e, 0x63,\n\t0x68, 0x12, 0x44, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f,\n\t0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e,\n\t0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x72, 0x65, 0x73,\n\t0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x05, 0x70, 0x61, 0x6e, 0x65, 0x6c,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68,\n\t0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65,\n\t0x6e, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x52, 0x05, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x1a, 0x3a,\n\t0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05,\n\t0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x77, 0x69, 0x64,\n\t0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x27, 0x0a, 0x05, 0x50, 0x61,\n\t0x6e, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,\n\t0x12, 0x07, 0x0a, 0x03, 0x49, 0x50, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x4c, 0x45,\n\t0x44, 0x10, 0x02, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61,\n\t0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62,\n\t0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_screen_message_proto_rawDescOnce sync.Once\n\tfile_screen_message_proto_rawDescData = file_screen_message_proto_rawDesc\n)\n\nfunc file_screen_message_proto_rawDescGZIP() []byte {\n\tfile_screen_message_proto_rawDescOnce.Do(func() {\n\t\tfile_screen_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_screen_message_proto_rawDescData)\n\t})\n\treturn file_screen_message_proto_rawDescData\n}\n\nvar file_screen_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_screen_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_screen_message_proto_goTypes = []interface{}{\n\t(Screen_Panel)(0),         // 0: techschool.pcbook.Screen.Panel\n\t(*Screen)(nil),            // 1: techschool.pcbook.Screen\n\t(*Screen_Resolution)(nil), // 2: techschool.pcbook.Screen.Resolution\n}\nvar file_screen_message_proto_depIdxs = []int32{\n\t2, // 0: techschool.pcbook.Screen.resolution:type_name -> techschool.pcbook.Screen.Resolution\n\t0, // 1: techschool.pcbook.Screen.panel:type_name -> techschool.pcbook.Screen.Panel\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_screen_message_proto_init() }\nfunc file_screen_message_proto_init() {\n\tif File_screen_message_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_screen_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Screen); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_screen_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Screen_Resolution); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_screen_message_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_screen_message_proto_goTypes,\n\t\tDependencyIndexes: file_screen_message_proto_depIdxs,\n\t\tEnumInfos:         file_screen_message_proto_enumTypes,\n\t\tMessageInfos:      file_screen_message_proto_msgTypes,\n\t}.Build()\n\tFile_screen_message_proto = out.File\n\tfile_screen_message_proto_rawDesc = nil\n\tfile_screen_message_proto_goTypes = nil\n\tfile_screen_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pb/storage_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.25.0\n// \tprotoc        v3.14.0\n// source: storage_message.proto\n\npackage pb\n\nimport (\n\tproto \"github.com/golang/protobuf/proto\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// This is a compile-time assertion that a sufficiently up-to-date version\n// of the legacy proto package is being used.\nconst _ = proto.ProtoPackageIsVersion4\n\ntype Storage_Driver int32\n\nconst (\n\tStorage_UNKNOWN Storage_Driver = 0\n\tStorage_HDD     Storage_Driver = 1\n\tStorage_SSD     Storage_Driver = 2\n)\n\n// Enum value maps for Storage_Driver.\nvar (\n\tStorage_Driver_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"HDD\",\n\t\t2: \"SSD\",\n\t}\n\tStorage_Driver_value = map[string]int32{\n\t\t\"UNKNOWN\": 0,\n\t\t\"HDD\":     1,\n\t\t\"SSD\":     2,\n\t}\n)\n\nfunc (x Storage_Driver) Enum() *Storage_Driver {\n\tp := new(Storage_Driver)\n\t*p = x\n\treturn p\n}\n\nfunc (x Storage_Driver) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Storage_Driver) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_storage_message_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Storage_Driver) Type() protoreflect.EnumType {\n\treturn &file_storage_message_proto_enumTypes[0]\n}\n\nfunc (x Storage_Driver) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Storage_Driver.Descriptor instead.\nfunc (Storage_Driver) EnumDescriptor() ([]byte, []int) {\n\treturn file_storage_message_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Storage struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDriver Storage_Driver `protobuf:\"varint,1,opt,name=driver,proto3,enum=techschool.pcbook.Storage_Driver\" json:\"driver,omitempty\"`\n\tMemory *Memory        `protobuf:\"bytes,2,opt,name=memory,proto3\" json:\"memory,omitempty\"`\n}\n\nfunc (x *Storage) Reset() {\n\t*x = Storage{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_storage_message_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Storage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Storage) ProtoMessage() {}\n\nfunc (x *Storage) ProtoReflect() protoreflect.Message {\n\tmi := &file_storage_message_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Storage.ProtoReflect.Descriptor instead.\nfunc (*Storage) Descriptor() ([]byte, []int) {\n\treturn file_storage_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Storage) GetDriver() Storage_Driver {\n\tif x != nil {\n\t\treturn x.Driver\n\t}\n\treturn Storage_UNKNOWN\n}\n\nfunc (x *Storage) GetMemory() *Memory {\n\tif x != nil {\n\t\treturn x.Memory\n\t}\n\treturn nil\n}\n\nvar File_storage_message_proto protoreflect.FileDescriptor\n\nvar file_storage_message_proto_rawDesc = []byte{\n\t0x0a, 0x15, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68,\n\t0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x1a, 0x14, 0x6d, 0x65, 0x6d, 0x6f,\n\t0x72, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x22, 0xa0, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x06,\n\t0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x74,\n\t0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b,\n\t0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x72, 0x69, 0x76, 0x65, 0x72, 0x52,\n\t0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72,\n\t0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63,\n\t0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x6d, 0x6f,\n\t0x72, 0x79, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x27, 0x0a, 0x06, 0x44, 0x72,\n\t0x69, 0x76, 0x65, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,\n\t0x00, 0x12, 0x07, 0x0a, 0x03, 0x48, 0x44, 0x44, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53,\n\t0x44, 0x10, 0x02, 0x42, 0x29, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x6c, 0x61,\n\t0x62, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x73, 0x63, 0x68, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x63, 0x62,\n\t0x6f, 0x6f, 0x6b, 0x2e, 0x70, 0x62, 0x50, 0x01, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_storage_message_proto_rawDescOnce sync.Once\n\tfile_storage_message_proto_rawDescData = file_storage_message_proto_rawDesc\n)\n\nfunc file_storage_message_proto_rawDescGZIP() []byte {\n\tfile_storage_message_proto_rawDescOnce.Do(func() {\n\t\tfile_storage_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_storage_message_proto_rawDescData)\n\t})\n\treturn file_storage_message_proto_rawDescData\n}\n\nvar file_storage_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_storage_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_storage_message_proto_goTypes = []interface{}{\n\t(Storage_Driver)(0), // 0: techschool.pcbook.Storage.Driver\n\t(*Storage)(nil),     // 1: techschool.pcbook.Storage\n\t(*Memory)(nil),      // 2: techschool.pcbook.Memory\n}\nvar file_storage_message_proto_depIdxs = []int32{\n\t0, // 0: techschool.pcbook.Storage.driver:type_name -> techschool.pcbook.Storage.Driver\n\t2, // 1: techschool.pcbook.Storage.memory:type_name -> techschool.pcbook.Memory\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_storage_message_proto_init() }\nfunc file_storage_message_proto_init() {\n\tif File_storage_message_proto != nil {\n\t\treturn\n\t}\n\tfile_memory_message_proto_init()\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_storage_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Storage); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_storage_message_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_storage_message_proto_goTypes,\n\t\tDependencyIndexes: file_storage_message_proto_depIdxs,\n\t\tEnumInfos:         file_storage_message_proto_enumTypes,\n\t\tMessageInfos:      file_storage_message_proto_msgTypes,\n\t}.Build()\n\tFile_storage_message_proto = out.File\n\tfile_storage_message_proto_rawDesc = nil\n\tfile_storage_message_proto_goTypes = nil\n\tfile_storage_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proto/auth_service.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\nimport \"google/api/annotations.proto\";\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nmessage LoginRequest {\n  string username = 1;\n  string password = 2;\n}\n\nmessage LoginResponse { string access_token = 1; }\n\nservice AuthService {\n  rpc Login(LoginRequest) returns (LoginResponse) {\n    option (google.api.http) = {\n      post : \"/v1/auth/login\"\n      body : \"*\"\n    };\n  };\n}"
  },
  {
    "path": "proto/filter_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nimport \"memory_message.proto\";\n\nmessage Filter {\n  double max_price_usd = 1;\n  uint32 min_cpu_cores = 2;\n  double min_cpu_ghz = 3;\n  Memory min_ram = 4;\n}\n"
  },
  {
    "path": "proto/google/api/annotations.proto",
    "content": "// Copyright (c) 2015, Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.api;\n\nimport \"google/api/http.proto\";\nimport \"google/protobuf/descriptor.proto\";\n\noption go_package = \"google.golang.org/genproto/googleapis/api/annotations;annotations\";\noption java_multiple_files = true;\noption java_outer_classname = \"AnnotationsProto\";\noption java_package = \"com.google.api\";\noption objc_class_prefix = \"GAPI\";\n\nextend google.protobuf.MethodOptions {\n  // See `HttpRule`.\n  HttpRule http = 72295728;\n}\n"
  },
  {
    "path": "proto/google/api/http.proto",
    "content": "// Copyright 2018 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.api;\n\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/genproto/googleapis/api/annotations;annotations\";\noption java_multiple_files = true;\noption java_outer_classname = \"HttpProto\";\noption java_package = \"com.google.api\";\noption objc_class_prefix = \"GAPI\";\n\n\n// Defines the HTTP configuration for an API service. It contains a list of\n// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method\n// to one or more HTTP REST API methods.\nmessage Http {\n  // A list of HTTP configuration rules that apply to individual API methods.\n  //\n  // **NOTE:** All service configuration rules follow \"last one wins\" order.\n  repeated HttpRule rules = 1;\n\n  // When set to true, URL path parmeters will be fully URI-decoded except in\n  // cases of single segment matches in reserved expansion, where \"%2F\" will be\n  // left encoded.\n  //\n  // The default behavior is to not decode RFC 6570 reserved characters in multi\n  // segment matches.\n  bool fully_decode_reserved_expansion = 2;\n}\n\n// `HttpRule` defines the mapping of an RPC method to one or more HTTP\n// REST API methods. The mapping specifies how different portions of the RPC\n// request message are mapped to URL path, URL query parameters, and\n// HTTP request body. The mapping is typically specified as an\n// `google.api.http` annotation on the RPC method,\n// see \"google/api/annotations.proto\" for details.\n//\n// The mapping consists of a field specifying the path template and\n// method kind.  The path template can refer to fields in the request\n// message, as in the example below which describes a REST GET\n// operation on a resource collection of messages:\n//\n//\n//     service Messaging {\n//       rpc GetMessage(GetMessageRequest) returns (Message) {\n//         option (google.api.http).get = \"/v1/messages/{message_id}/{sub.subfield}\";\n//       }\n//     }\n//     message GetMessageRequest {\n//       message SubMessage {\n//         string subfield = 1;\n//       }\n//       string message_id = 1; // mapped to the URL\n//       SubMessage sub = 2;    // `sub.subfield` is url-mapped\n//     }\n//     message Message {\n//       string text = 1; // content of the resource\n//     }\n//\n// The same http annotation can alternatively be expressed inside the\n// `GRPC API Configuration` YAML file.\n//\n//     http:\n//       rules:\n//         - selector: <proto_package_name>.Messaging.GetMessage\n//           get: /v1/messages/{message_id}/{sub.subfield}\n//\n// This definition enables an automatic, bidrectional mapping of HTTP\n// JSON to RPC. Example:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456/foo`  | `GetMessage(message_id: \"123456\" sub: SubMessage(subfield: \"foo\"))`\n//\n// In general, not only fields but also field paths can be referenced\n// from a path pattern. Fields mapped to the path pattern cannot be\n// repeated and must have a primitive (non-message) type.\n//\n// Any fields in the request message which are not bound by the path\n// pattern automatically become (optional) HTTP query\n// parameters. Assume the following definition of the request message:\n//\n//\n//     service Messaging {\n//       rpc GetMessage(GetMessageRequest) returns (Message) {\n//         option (google.api.http).get = \"/v1/messages/{message_id}\";\n//       }\n//     }\n//     message GetMessageRequest {\n//       message SubMessage {\n//         string subfield = 1;\n//       }\n//       string message_id = 1; // mapped to the URL\n//       int64 revision = 2;    // becomes a parameter\n//       SubMessage sub = 3;    // `sub.subfield` becomes a parameter\n//     }\n//\n//\n// This enables a HTTP JSON to RPC mapping as below:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: \"123456\" revision: 2 sub: SubMessage(subfield: \"foo\"))`\n//\n// Note that fields which are mapped to HTTP parameters must have a\n// primitive type or a repeated primitive type. Message types are not\n// allowed. In the case of a repeated type, the parameter can be\n// repeated in the URL, as in `...?param=A&param=B`.\n//\n// For HTTP method kinds which allow a request body, the `body` field\n// specifies the mapping. Consider a REST update method on the\n// message resource collection:\n//\n//\n//     service Messaging {\n//       rpc UpdateMessage(UpdateMessageRequest) returns (Message) {\n//         option (google.api.http) = {\n//           put: \"/v1/messages/{message_id}\"\n//           body: \"message\"\n//         };\n//       }\n//     }\n//     message UpdateMessageRequest {\n//       string message_id = 1; // mapped to the URL\n//       Message message = 2;   // mapped to the body\n//     }\n//\n//\n// The following HTTP JSON to RPC mapping is enabled, where the\n// representation of the JSON in the request body is determined by\n// protos JSON encoding:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" message { text: \"Hi!\" })`\n//\n// The special name `*` can be used in the body mapping to define that\n// every field not bound by the path template should be mapped to the\n// request body.  This enables the following alternative definition of\n// the update method:\n//\n//     service Messaging {\n//       rpc UpdateMessage(Message) returns (Message) {\n//         option (google.api.http) = {\n//           put: \"/v1/messages/{message_id}\"\n//           body: \"*\"\n//         };\n//       }\n//     }\n//     message Message {\n//       string message_id = 1;\n//       string text = 2;\n//     }\n//\n//\n// The following HTTP JSON to RPC mapping is enabled:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" text: \"Hi!\")`\n//\n// Note that when using `*` in the body mapping, it is not possible to\n// have HTTP parameters, as all fields not bound by the path end in\n// the body. This makes this option more rarely used in practice of\n// defining REST APIs. The common usage of `*` is in custom methods\n// which don't use the URL at all for transferring data.\n//\n// It is possible to define multiple HTTP methods for one RPC by using\n// the `additional_bindings` option. Example:\n//\n//     service Messaging {\n//       rpc GetMessage(GetMessageRequest) returns (Message) {\n//         option (google.api.http) = {\n//           get: \"/v1/messages/{message_id}\"\n//           additional_bindings {\n//             get: \"/v1/users/{user_id}/messages/{message_id}\"\n//           }\n//         };\n//       }\n//     }\n//     message GetMessageRequest {\n//       string message_id = 1;\n//       string user_id = 2;\n//     }\n//\n//\n// This enables the following two alternative HTTP JSON to RPC\n// mappings:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456` | `GetMessage(message_id: \"123456\")`\n// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: \"me\" message_id: \"123456\")`\n//\n// # Rules for HTTP mapping\n//\n// The rules for mapping HTTP path, query parameters, and body fields\n// to the request message are as follows:\n//\n// 1. The `body` field specifies either `*` or a field path, or is\n//    omitted. If omitted, it indicates there is no HTTP request body.\n// 2. Leaf fields (recursive expansion of nested messages in the\n//    request) can be classified into three types:\n//     (a) Matched in the URL template.\n//     (b) Covered by body (if body is `*`, everything except (a) fields;\n//         else everything under the body field)\n//     (c) All other fields.\n// 3. URL query parameters found in the HTTP request are mapped to (c) fields.\n// 4. Any body sent with an HTTP request can contain only (b) fields.\n//\n// The syntax of the path template is as follows:\n//\n//     Template = \"/\" Segments [ Verb ] ;\n//     Segments = Segment { \"/\" Segment } ;\n//     Segment  = \"*\" | \"**\" | LITERAL | Variable ;\n//     Variable = \"{\" FieldPath [ \"=\" Segments ] \"}\" ;\n//     FieldPath = IDENT { \".\" IDENT } ;\n//     Verb     = \":\" LITERAL ;\n//\n// The syntax `*` matches a single path segment. The syntax `**` matches zero\n// or more path segments, which must be the last part of the path except the\n// `Verb`. The syntax `LITERAL` matches literal text in the path.\n//\n// The syntax `Variable` matches part of the URL path as specified by its\n// template. A variable template must not contain other variables. If a variable\n// matches a single path segment, its template may be omitted, e.g. `{var}`\n// is equivalent to `{var=*}`.\n//\n// If a variable contains exactly one path segment, such as `\"{var}\"` or\n// `\"{var=*}\"`, when such a variable is expanded into a URL path, all characters\n// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the\n// Discovery Document as `{var}`.\n//\n// If a variable contains one or more path segments, such as `\"{var=foo/*}\"`\n// or `\"{var=**}\"`, when such a variable is expanded into a URL path, all\n// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables\n// show up in the Discovery Document as `{+var}`.\n//\n// NOTE: While the single segment variable matches the semantics of\n// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2\n// Simple String Expansion, the multi segment variable **does not** match\n// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion\n// does not expand special characters like `?` and `#`, which would lead\n// to invalid URLs.\n//\n// NOTE: the field paths in variables and in the `body` must not refer to\n// repeated fields or map fields.\nmessage HttpRule {\n  // Selects methods to which this rule applies.\n  //\n  // Refer to [selector][google.api.DocumentationRule.selector] for syntax details.\n  string selector = 1;\n\n  // Determines the URL pattern is matched by this rules. This pattern can be\n  // used with any of the {get|put|post|delete|patch} methods. A custom method\n  // can be defined using the 'custom' field.\n  oneof pattern {\n    // Used for listing and getting information about resources.\n    string get = 2;\n\n    // Used for updating a resource.\n    string put = 3;\n\n    // Used for creating a resource.\n    string post = 4;\n\n    // Used for deleting a resource.\n    string delete = 5;\n\n    // Used for updating a resource.\n    string patch = 6;\n\n    // The custom pattern is used for specifying an HTTP method that is not\n    // included in the `pattern` field, such as HEAD, or \"*\" to leave the\n    // HTTP method unspecified for this rule. The wild-card rule is useful\n    // for services that provide content to Web (HTML) clients.\n    CustomHttpPattern custom = 8;\n  }\n\n  // The name of the request field whose value is mapped to the HTTP body, or\n  // `*` for mapping all fields not captured by the path pattern to the HTTP\n  // body. NOTE: the referred field must not be a repeated field and must be\n  // present at the top-level of request message type.\n  string body = 7;\n\n  // Optional. The name of the response field whose value is mapped to the HTTP\n  // body of response. Other response fields are ignored. When\n  // not set, the response message will be used as HTTP body of response.\n  string response_body = 12;\n\n  // Additional HTTP bindings for the selector. Nested bindings must\n  // not contain an `additional_bindings` field themselves (that is,\n  // the nesting may only be one level deep).\n  repeated HttpRule additional_bindings = 11;\n}\n\n// A custom pattern is used for defining custom HTTP verb.\nmessage CustomHttpPattern {\n  // The name of this custom HTTP verb.\n  string kind = 1;\n\n  // The path matched by this custom verb.\n  string path = 2;\n}\n"
  },
  {
    "path": "proto/google/api/httpbody.proto",
    "content": "// Copyright 2018 Google LLC.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\nsyntax = \"proto3\";\n\npackage google.api;\n\nimport \"google/protobuf/any.proto\";\n\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/genproto/googleapis/api/httpbody;httpbody\";\noption java_multiple_files = true;\noption java_outer_classname = \"HttpBodyProto\";\noption java_package = \"com.google.api\";\noption objc_class_prefix = \"GAPI\";\n\n// Message that represents an arbitrary HTTP body. It should only be used for\n// payload formats that can't be represented as JSON, such as raw binary or\n// an HTML page.\n//\n//\n// This message can be used both in streaming and non-streaming API methods in\n// the request as well as the response.\n//\n// It can be used as a top-level request field, which is convenient if one\n// wants to extract parameters from either the URL or HTTP template into the\n// request fields and also want access to the raw HTTP body.\n//\n// Example:\n//\n//     message GetResourceRequest {\n//       // A unique request id.\n//       string request_id = 1;\n//\n//       // The raw HTTP body is bound to this field.\n//       google.api.HttpBody http_body = 2;\n//     }\n//\n//     service ResourceService {\n//       rpc GetResource(GetResourceRequest) returns (google.api.HttpBody);\n//       rpc UpdateResource(google.api.HttpBody) returns\n//       (google.protobuf.Empty);\n//     }\n//\n// Example with streaming methods:\n//\n//     service CaldavService {\n//       rpc GetCalendar(stream google.api.HttpBody)\n//         returns (stream google.api.HttpBody);\n//       rpc UpdateCalendar(stream google.api.HttpBody)\n//         returns (stream google.api.HttpBody);\n//     }\n//\n// Use of this type only changes how the request and response bodies are\n// handled, all other features will continue to work unchanged.\nmessage HttpBody {\n  // The HTTP Content-Type header value specifying the content type of the body.\n  string content_type = 1;\n\n  // The HTTP request/response body as raw binary.\n  bytes data = 2;\n\n  // Application specific response metadata. Must be set in the first response\n  // for streaming APIs.\n  repeated google.protobuf.Any extensions = 3;\n}"
  },
  {
    "path": "proto/google/rpc/code.proto",
    "content": "// Copyright 2017 Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.rpc;\n\noption go_package = \"google.golang.org/genproto/googleapis/rpc/code;code\";\noption java_multiple_files = true;\noption java_outer_classname = \"CodeProto\";\noption java_package = \"com.google.rpc\";\noption objc_class_prefix = \"RPC\";\n\n\n// The canonical error codes for Google APIs.\n//\n//\n// Sometimes multiple error codes may apply.  Services should return\n// the most specific error code that applies.  For example, prefer\n// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.\n// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.\nenum Code {\n  // Not an error; returned on success\n  //\n  // HTTP Mapping: 200 OK\n  OK = 0;\n\n  // The operation was cancelled, typically by the caller.\n  //\n  // HTTP Mapping: 499 Client Closed Request\n  CANCELLED = 1;\n\n  // Unknown error.  For example, this error may be returned when\n  // a `Status` value received from another address space belongs to\n  // an error space that is not known in this address space.  Also\n  // errors raised by APIs that do not return enough error information\n  // may be converted to this error.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  UNKNOWN = 2;\n\n  // The client specified an invalid argument.  Note that this differs\n  // from `FAILED_PRECONDITION`.  `INVALID_ARGUMENT` indicates arguments\n  // that are problematic regardless of the state of the system\n  // (e.g., a malformed file name).\n  //\n  // HTTP Mapping: 400 Bad Request\n  INVALID_ARGUMENT = 3;\n\n  // The deadline expired before the operation could complete. For operations\n  // that change the state of the system, this error may be returned\n  // even if the operation has completed successfully.  For example, a\n  // successful response from a server could have been delayed long\n  // enough for the deadline to expire.\n  //\n  // HTTP Mapping: 504 Gateway Timeout\n  DEADLINE_EXCEEDED = 4;\n\n  // Some requested entity (e.g., file or directory) was not found.\n  //\n  // Note to server developers: if a request is denied for an entire class\n  // of users, such as gradual feature rollout or undocumented whitelist,\n  // `NOT_FOUND` may be used. If a request is denied for some users within\n  // a class of users, such as user-based access control, `PERMISSION_DENIED`\n  // must be used.\n  //\n  // HTTP Mapping: 404 Not Found\n  NOT_FOUND = 5;\n\n  // The entity that a client attempted to create (e.g., file or directory)\n  // already exists.\n  //\n  // HTTP Mapping: 409 Conflict\n  ALREADY_EXISTS = 6;\n\n  // The caller does not have permission to execute the specified\n  // operation. `PERMISSION_DENIED` must not be used for rejections\n  // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`\n  // instead for those errors). `PERMISSION_DENIED` must not be\n  // used if the caller can not be identified (use `UNAUTHENTICATED`\n  // instead for those errors). This error code does not imply the\n  // request is valid or the requested entity exists or satisfies\n  // other pre-conditions.\n  //\n  // HTTP Mapping: 403 Forbidden\n  PERMISSION_DENIED = 7;\n\n  // The request does not have valid authentication credentials for the\n  // operation.\n  //\n  // HTTP Mapping: 401 Unauthorized\n  UNAUTHENTICATED = 16;\n\n  // Some resource has been exhausted, perhaps a per-user quota, or\n  // perhaps the entire file system is out of space.\n  //\n  // HTTP Mapping: 429 Too Many Requests\n  RESOURCE_EXHAUSTED = 8;\n\n  // The operation was rejected because the system is not in a state\n  // required for the operation's execution.  For example, the directory\n  // to be deleted is non-empty, an rmdir operation is applied to\n  // a non-directory, etc.\n  //\n  // Service implementors can use the following guidelines to decide\n  // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:\n  //  (a) Use `UNAVAILABLE` if the client can retry just the failing call.\n  //  (b) Use `ABORTED` if the client should retry at a higher level\n  //      (e.g., when a client-specified test-and-set fails, indicating the\n  //      client should restart a read-modify-write sequence).\n  //  (c) Use `FAILED_PRECONDITION` if the client should not retry until\n  //      the system state has been explicitly fixed.  E.g., if an \"rmdir\"\n  //      fails because the directory is non-empty, `FAILED_PRECONDITION`\n  //      should be returned since the client should not retry unless\n  //      the files are deleted from the directory.\n  //\n  // HTTP Mapping: 400 Bad Request\n  FAILED_PRECONDITION = 9;\n\n  // The operation was aborted, typically due to a concurrency issue such as\n  // a sequencer check failure or transaction abort.\n  //\n  // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n  // `ABORTED`, and `UNAVAILABLE`.\n  //\n  // HTTP Mapping: 409 Conflict\n  ABORTED = 10;\n\n  // The operation was attempted past the valid range.  E.g., seeking or\n  // reading past end-of-file.\n  //\n  // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may\n  // be fixed if the system state changes. For example, a 32-bit file\n  // system will generate `INVALID_ARGUMENT` if asked to read at an\n  // offset that is not in the range [0,2^32-1], but it will generate\n  // `OUT_OF_RANGE` if asked to read from an offset past the current\n  // file size.\n  //\n  // There is a fair bit of overlap between `FAILED_PRECONDITION` and\n  // `OUT_OF_RANGE`.  We recommend using `OUT_OF_RANGE` (the more specific\n  // error) when it applies so that callers who are iterating through\n  // a space can easily look for an `OUT_OF_RANGE` error to detect when\n  // they are done.\n  //\n  // HTTP Mapping: 400 Bad Request\n  OUT_OF_RANGE = 11;\n\n  // The operation is not implemented or is not supported/enabled in this\n  // service.\n  //\n  // HTTP Mapping: 501 Not Implemented\n  UNIMPLEMENTED = 12;\n\n  // Internal errors.  This means that some invariants expected by the\n  // underlying system have been broken.  This error code is reserved\n  // for serious errors.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  INTERNAL = 13;\n\n  // The service is currently unavailable.  This is most likely a\n  // transient condition, which can be corrected by retrying with\n  // a backoff.\n  //\n  // See the guidelines above for deciding between `FAILED_PRECONDITION`,\n  // `ABORTED`, and `UNAVAILABLE`.\n  //\n  // HTTP Mapping: 503 Service Unavailable\n  UNAVAILABLE = 14;\n\n  // Unrecoverable data loss or corruption.\n  //\n  // HTTP Mapping: 500 Internal Server Error\n  DATA_LOSS = 15;\n}\n"
  },
  {
    "path": "proto/google/rpc/error_details.proto",
    "content": "// Copyright 2017 Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.rpc;\n\nimport \"google/protobuf/duration.proto\";\n\noption go_package = \"google.golang.org/genproto/googleapis/rpc/errdetails;errdetails\";\noption java_multiple_files = true;\noption java_outer_classname = \"ErrorDetailsProto\";\noption java_package = \"com.google.rpc\";\noption objc_class_prefix = \"RPC\";\n\n\n// Describes when the clients can retry a failed request. Clients could ignore\n// the recommendation here or retry when this information is missing from error\n// responses.\n//\n// It's always recommended that clients should use exponential backoff when\n// retrying.\n//\n// Clients should wait until `retry_delay` amount of time has passed since\n// receiving the error response before retrying.  If retrying requests also\n// fail, clients should use an exponential backoff scheme to gradually increase\n// the delay between retries based on `retry_delay`, until either a maximum\n// number of retires have been reached or a maximum retry delay cap has been\n// reached.\nmessage RetryInfo {\n  // Clients should wait at least this long between retrying the same request.\n  google.protobuf.Duration retry_delay = 1;\n}\n\n// Describes additional debugging info.\nmessage DebugInfo {\n  // The stack trace entries indicating where the error occurred.\n  repeated string stack_entries = 1;\n\n  // Additional debugging information provided by the server.\n  string detail = 2;\n}\n\n// Describes how a quota check failed.\n//\n// For example if a daily limit was exceeded for the calling project,\n// a service could respond with a QuotaFailure detail containing the project\n// id and the description of the quota limit that was exceeded.  If the\n// calling project hasn't enabled the service in the developer console, then\n// a service could respond with the project id and set `service_disabled`\n// to true.\n//\n// Also see RetryDetail and Help types for other details about handling a\n// quota failure.\nmessage QuotaFailure {\n  // A message type used to describe a single quota violation.  For example, a\n  // daily quota or a custom quota that was exceeded.\n  message Violation {\n    // The subject on which the quota check failed.\n    // For example, \"clientip:<ip address of client>\" or \"project:<Google\n    // developer project id>\".\n    string subject = 1;\n\n    // A description of how the quota check failed. Clients can use this\n    // description to find more about the quota configuration in the service's\n    // public documentation, or find the relevant quota limit to adjust through\n    // developer console.\n    //\n    // For example: \"Service disabled\" or \"Daily Limit for read operations\n    // exceeded\".\n    string description = 2;\n  }\n\n  // Describes all quota violations.\n  repeated Violation violations = 1;\n}\n\n// Describes what preconditions have failed.\n//\n// For example, if an RPC failed because it required the Terms of Service to be\n// acknowledged, it could list the terms of service violation in the\n// PreconditionFailure message.\nmessage PreconditionFailure {\n  // A message type used to describe a single precondition failure.\n  message Violation {\n    // The type of PreconditionFailure. We recommend using a service-specific\n    // enum type to define the supported precondition violation types. For\n    // example, \"TOS\" for \"Terms of Service violation\".\n    string type = 1;\n\n    // The subject, relative to the type, that failed.\n    // For example, \"google.com/cloud\" relative to the \"TOS\" type would\n    // indicate which terms of service is being referenced.\n    string subject = 2;\n\n    // A description of how the precondition failed. Developers can use this\n    // description to understand how to fix the failure.\n    //\n    // For example: \"Terms of service not accepted\".\n    string description = 3;\n  }\n\n  // Describes all precondition violations.\n  repeated Violation violations = 1;\n}\n\n// Describes violations in a client request. This error type focuses on the\n// syntactic aspects of the request.\nmessage BadRequest {\n  // A message type used to describe a single bad request field.\n  message FieldViolation {\n    // A path leading to a field in the request body. The value will be a\n    // sequence of dot-separated identifiers that identify a protocol buffer\n    // field. E.g., \"field_violations.field\" would identify this field.\n    string field = 1;\n\n    // A description of why the request element is bad.\n    string description = 2;\n  }\n\n  // Describes all violations in a client request.\n  repeated FieldViolation field_violations = 1;\n}\n\n// Contains metadata about the request that clients can attach when filing a bug\n// or providing other forms of feedback.\nmessage RequestInfo {\n  // An opaque string that should only be interpreted by the service generating\n  // it. For example, it can be used to identify requests in the service's logs.\n  string request_id = 1;\n\n  // Any data that was used to serve this request. For example, an encrypted\n  // stack trace that can be sent back to the service provider for debugging.\n  string serving_data = 2;\n}\n\n// Describes the resource that is being accessed.\nmessage ResourceInfo {\n  // A name for the type of resource being accessed, e.g. \"sql table\",\n  // \"cloud storage bucket\", \"file\", \"Google calendar\"; or the type URL\n  // of the resource: e.g. \"type.googleapis.com/google.pubsub.v1.Topic\".\n  string resource_type = 1;\n\n  // The name of the resource being accessed.  For example, a shared calendar\n  // name: \"example.com_4fghdhgsrgh@group.calendar.google.com\", if the current\n  // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].\n  string resource_name = 2;\n\n  // The owner of the resource (optional).\n  // For example, \"user:<owner email>\" or \"project:<Google developer project\n  // id>\".\n  string owner = 3;\n\n  // Describes what error is encountered when accessing this resource.\n  // For example, updating a cloud project may require the `writer` permission\n  // on the developer console project.\n  string description = 4;\n}\n\n// Provides links to documentation or for performing an out of band action.\n//\n// For example, if a quota check failed with an error indicating the calling\n// project hasn't enabled the accessed service, this can contain a URL pointing\n// directly to the right place in the developer console to flip the bit.\nmessage Help {\n  // Describes a URL link.\n  message Link {\n    // Describes what the link offers.\n    string description = 1;\n\n    // The URL of the link.\n    string url = 2;\n  }\n\n  // URL(s) pointing to additional information on handling the current error.\n  repeated Link links = 1;\n}\n\n// Provides a localized error message that is safe to return to the user\n// which can be attached to an RPC error.\nmessage LocalizedMessage {\n  // The locale used following the specification defined at\n  // http://www.rfc-editor.org/rfc/bcp/bcp47.txt.\n  // Examples are: \"en-US\", \"fr-CH\", \"es-MX\"\n  string locale = 1;\n\n  // The localized error message in the above locale.\n  string message = 2;\n}\n"
  },
  {
    "path": "proto/google/rpc/status.proto",
    "content": "// Copyright 2017 Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.rpc;\n\nimport \"google/protobuf/any.proto\";\n\noption go_package = \"google.golang.org/genproto/googleapis/rpc/status;status\";\noption java_multiple_files = true;\noption java_outer_classname = \"StatusProto\";\noption java_package = \"com.google.rpc\";\noption objc_class_prefix = \"RPC\";\n\n\n// The `Status` type defines a logical error model that is suitable for different\n// programming environments, including REST APIs and RPC APIs. It is used by\n// [gRPC](https://github.com/grpc). The error model is designed to be:\n//\n// - Simple to use and understand for most users\n// - Flexible enough to meet unexpected needs\n//\n// # Overview\n//\n// The `Status` message contains three pieces of data: error code, error message,\n// and error details. The error code should be an enum value of\n// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed.  The\n// error message should be a developer-facing English message that helps\n// developers *understand* and *resolve* the error. If a localized user-facing\n// error message is needed, put the localized message in the error details or\n// localize it in the client. The optional error details may contain arbitrary\n// information about the error. There is a predefined set of error detail types\n// in the package `google.rpc` that can be used for common error conditions.\n//\n// # Language mapping\n//\n// The `Status` message is the logical representation of the error model, but it\n// is not necessarily the actual wire format. When the `Status` message is\n// exposed in different client libraries and different wire protocols, it can be\n// mapped differently. For example, it will likely be mapped to some exceptions\n// in Java, but more likely mapped to some error codes in C.\n//\n// # Other uses\n//\n// The error model and the `Status` message can be used in a variety of\n// environments, either with or without APIs, to provide a\n// consistent developer experience across different environments.\n//\n// Example uses of this error model include:\n//\n// - Partial errors. If a service needs to return partial errors to the client,\n//     it may embed the `Status` in the normal response to indicate the partial\n//     errors.\n//\n// - Workflow errors. A typical workflow has multiple steps. Each step may\n//     have a `Status` message for error reporting.\n//\n// - Batch operations. If a client uses batch request and batch response, the\n//     `Status` message should be used directly inside batch response, one for\n//     each error sub-response.\n//\n// - Asynchronous operations. If an API call embeds asynchronous operation\n//     results in its response, the status of those operations should be\n//     represented directly using the `Status` message.\n//\n// - Logging. If some API errors are stored in logs, the message `Status` could\n//     be used directly after any stripping needed for security/privacy reasons.\nmessage Status {\n  // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].\n  int32 code = 1;\n\n  // A developer-facing error message, which should be in English. Any\n  // user-facing error message should be localized and sent in the\n  // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.\n  string message = 2;\n\n  // A list of messages that carry the error details.  There is a common set of\n  // message types for APIs to use.\n  repeated google.protobuf.Any details = 3;\n}\n"
  },
  {
    "path": "proto/keyboard_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nmessage Keyboard {\n  enum Layout {\n    UNKNOWN = 0;\n    QWERTY = 1;\n    QWERTZ = 2;\n    AZERTY = 3;\n  }\n\n  Layout layout = 1;\n  bool backlit = 2;\n}\n"
  },
  {
    "path": "proto/laptop_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nimport \"processor_message.proto\";\nimport \"memory_message.proto\";\nimport \"storage_message.proto\";\nimport \"screen_message.proto\";\nimport \"keyboard_message.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\nmessage Laptop {\n  string id = 1;\n  string brand = 2;\n  string name = 3;\n  CPU cpu = 4;\n  Memory ram = 5;\n  repeated GPU gpus = 6;\n  repeated Storage storages = 7;\n  Screen screen = 8;\n  Keyboard keyboard = 9;\n  oneof weight {\n    double weight_kg = 10;\n    double weight_lb = 11;\n  }\n  double price_usd = 12;\n  uint32 release_year = 13;\n  google.protobuf.Timestamp updated_at = 14;\n}\n"
  },
  {
    "path": "proto/laptop_service.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nimport \"laptop_message.proto\";\nimport \"filter_message.proto\";\nimport \"google/api/annotations.proto\";\n\nmessage CreateLaptopRequest { Laptop laptop = 1; }\n\nmessage CreateLaptopResponse { string id = 1; }\n\nmessage SearchLaptopRequest { Filter filter = 1; }\n\nmessage SearchLaptopResponse { Laptop laptop = 1; }\n\nmessage UploadImageRequest {\n  oneof data {\n    ImageInfo info = 1;\n    bytes chunk_data = 2;\n  };\n}\n\nmessage ImageInfo {\n  string laptop_id = 1;\n  string image_type = 2;\n}\n\nmessage UploadImageResponse {\n  string id = 1;\n  uint32 size = 2;\n}\n\nmessage RateLaptopRequest {\n  string laptop_id = 1;\n  double score = 2;\n}\n\nmessage RateLaptopResponse {\n  string laptop_id = 1;\n  uint32 rated_count = 2;\n  double average_score = 3;\n}\n\nservice LaptopService {\n  rpc CreateLaptop(CreateLaptopRequest) returns (CreateLaptopResponse) {\n    option (google.api.http) = {\n      post : \"/v1/laptop/create\"\n      body : \"*\"\n    };\n  };\n  rpc SearchLaptop(SearchLaptopRequest) returns (stream SearchLaptopResponse) {\n    option (google.api.http) = {\n      get : \"/v1/laptop/search\"\n    };\n  };\n  rpc UploadImage(stream UploadImageRequest) returns (UploadImageResponse) {\n    option (google.api.http) = {\n      post : \"/v1/laptop/upload_image\"\n      body : \"*\"\n    };\n  };\n  rpc RateLaptop(stream RateLaptopRequest) returns (stream RateLaptopResponse) {\n    option (google.api.http) = {\n      post : \"/v1/laptop/rate\"\n      body : \"*\"\n    };\n  };\n}\n"
  },
  {
    "path": "proto/memory_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nmessage Memory {\n  enum Unit {\n    UNKNOWN = 0;\n    BIT = 1;\n    BYTE = 2;\n    KILOBYTE = 3;\n    MEGABYTE = 4;\n    GIGABYTE = 5;\n    TERABYTE = 6;\n  }\n\n  uint64 value = 1;\n  Unit unit = 2;\n}\n"
  },
  {
    "path": "proto/processor_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nimport \"memory_message.proto\";\n\nmessage CPU {\n  string brand = 1;\n  string name = 2;\n  uint32 number_cores = 3;\n  uint32 number_threads = 4;\n  double min_ghz = 5;\n  double max_ghz = 6;\n}\n\nmessage GPU {\n  string brand = 1;\n  string name = 2;\n  double min_ghz = 3;\n  double max_ghz = 4;\n  Memory memory = 5;\n}\n"
  },
  {
    "path": "proto/screen_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nmessage Screen {\n  message Resolution {\n    uint32 width = 1;\n    uint32 height = 2;\n  }\n\n  enum Panel {\n    UNKNOWN = 0;\n    IPS = 1;\n    OLED = 2;\n  }\n\n  float size_inch = 1;\n  Resolution resolution = 2;\n  Panel panel = 3;\n  bool multitouch = 4;\n}\n"
  },
  {
    "path": "proto/storage_message.proto",
    "content": "syntax = \"proto3\";\n\npackage techschool.pcbook;\n\noption go_package = \".;pb\";\noption java_package = \"com.gitlab.techschool.pcbook.pb\";\noption java_multiple_files = true;\n\nimport \"memory_message.proto\";\n\nmessage Storage {\n  enum Driver {\n    UNKNOWN = 0;\n    HDD = 1;\n    SSD = 2;\n  }\n\n  Driver driver = 1;\n  Memory memory = 2;\n}\n"
  },
  {
    "path": "sample/laptop.go",
    "content": "package sample\n\nimport (\n\t\"github.com/golang/protobuf/ptypes\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n)\n\n// NewKeyboard returns a new sample keyboard\nfunc NewKeyboard() *pb.Keyboard {\n\tkeyboard := &pb.Keyboard{\n\t\tLayout:  randomKeyboardLayout(),\n\t\tBacklit: randomBool(),\n\t}\n\n\treturn keyboard\n}\n\n// NewCPU returns a new sample CPU\nfunc NewCPU() *pb.CPU {\n\tbrand := randomCPUBrand()\n\tname := randomCPUName(brand)\n\n\tnumberCores := randomInt(2, 8)\n\tnumberThreads := randomInt(numberCores, 12)\n\n\tminGhz := randomFloat64(2.0, 3.5)\n\tmaxGhz := randomFloat64(minGhz, 5.0)\n\n\tcpu := &pb.CPU{\n\t\tBrand:         brand,\n\t\tName:          name,\n\t\tNumberCores:   uint32(numberCores),\n\t\tNumberThreads: uint32(numberThreads),\n\t\tMinGhz:        minGhz,\n\t\tMaxGhz:        maxGhz,\n\t}\n\n\treturn cpu\n}\n\n// NewGPU returns a new sample GPU\nfunc NewGPU() *pb.GPU {\n\tbrand := randomGPUBrand()\n\tname := randomGPUName(brand)\n\n\tminGhz := randomFloat64(1.0, 1.5)\n\tmaxGhz := randomFloat64(minGhz, 2.0)\n\tmemGB := randomInt(2, 6)\n\n\tgpu := &pb.GPU{\n\t\tBrand:  brand,\n\t\tName:   name,\n\t\tMinGhz: minGhz,\n\t\tMaxGhz: maxGhz,\n\t\tMemory: &pb.Memory{\n\t\t\tValue: uint64(memGB),\n\t\t\tUnit:  pb.Memory_GIGABYTE,\n\t\t},\n\t}\n\n\treturn gpu\n}\n\n// NewRAM returns a new sample RAM\nfunc NewRAM() *pb.Memory {\n\tmemGB := randomInt(4, 64)\n\n\tram := &pb.Memory{\n\t\tValue: uint64(memGB),\n\t\tUnit:  pb.Memory_GIGABYTE,\n\t}\n\n\treturn ram\n}\n\n// NewSSD returns a new sample SSD\nfunc NewSSD() *pb.Storage {\n\tmemGB := randomInt(128, 1024)\n\n\tssd := &pb.Storage{\n\t\tDriver: pb.Storage_SSD,\n\t\tMemory: &pb.Memory{\n\t\t\tValue: uint64(memGB),\n\t\t\tUnit:  pb.Memory_GIGABYTE,\n\t\t},\n\t}\n\n\treturn ssd\n}\n\n// NewHDD returns a new sample HDD\nfunc NewHDD() *pb.Storage {\n\tmemTB := randomInt(1, 6)\n\n\thdd := &pb.Storage{\n\t\tDriver: pb.Storage_HDD,\n\t\tMemory: &pb.Memory{\n\t\t\tValue: uint64(memTB),\n\t\t\tUnit:  pb.Memory_TERABYTE,\n\t\t},\n\t}\n\n\treturn hdd\n}\n\n// NewScreen returns a new sample Screen\nfunc NewScreen() *pb.Screen {\n\tscreen := &pb.Screen{\n\t\tSizeInch:   randomFloat32(13, 17),\n\t\tResolution: randomScreenResolution(),\n\t\tPanel:      randomScreenPanel(),\n\t\tMultitouch: randomBool(),\n\t}\n\n\treturn screen\n}\n\n// NewLaptop returns a new sample Laptop\nfunc NewLaptop() *pb.Laptop {\n\tbrand := randomLaptopBrand()\n\tname := randomLaptopName(brand)\n\n\tlaptop := &pb.Laptop{\n\t\tId:       randomID(),\n\t\tBrand:    brand,\n\t\tName:     name,\n\t\tCpu:      NewCPU(),\n\t\tRam:      NewRAM(),\n\t\tGpus:     []*pb.GPU{NewGPU()},\n\t\tStorages: []*pb.Storage{NewSSD(), NewHDD()},\n\t\tScreen:   NewScreen(),\n\t\tKeyboard: NewKeyboard(),\n\t\tWeight: &pb.Laptop_WeightKg{\n\t\t\tWeightKg: randomFloat64(1.0, 3.0),\n\t\t},\n\t\tPriceUsd:    randomFloat64(1500, 3500),\n\t\tReleaseYear: uint32(randomInt(2015, 2019)),\n\t\tUpdatedAt:   ptypes.TimestampNow(),\n\t}\n\n\treturn laptop\n}\n\n// RandomLaptopScore returns a random laptop score\nfunc RandomLaptopScore() float64 {\n\treturn float64(randomInt(1, 10))\n}\n"
  },
  {
    "path": "sample/random.go",
    "content": "package sample\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n)\n\nfunc init() {\n\trand.Seed(time.Now().UnixNano())\n}\n\nfunc randomStringFromSet(a ...string) string {\n\tn := len(a)\n\tif n == 0 {\n\t\treturn \"\"\n\t}\n\treturn a[rand.Intn(n)]\n}\n\nfunc randomBool() bool {\n\treturn rand.Intn(2) == 1\n}\n\nfunc randomInt(min, max int) int {\n\treturn min + rand.Int()%(max-min+1)\n}\n\nfunc randomFloat64(min, max float64) float64 {\n\treturn min + rand.Float64()*(max-min)\n}\n\nfunc randomFloat32(min, max float32) float32 {\n\treturn min + rand.Float32()*(max-min)\n}\n\nfunc randomID() string {\n\treturn uuid.New().String()\n}\n\nfunc randomKeyboardLayout() pb.Keyboard_Layout {\n\tswitch rand.Intn(3) {\n\tcase 1:\n\t\treturn pb.Keyboard_QWERTY\n\tcase 2:\n\t\treturn pb.Keyboard_QWERTZ\n\tdefault:\n\t\treturn pb.Keyboard_AZERTY\n\t}\n}\n\nfunc randomScreenResolution() *pb.Screen_Resolution {\n\theight := randomInt(1080, 4320)\n\twidth := height * 16 / 9\n\n\tresolution := &pb.Screen_Resolution{\n\t\tWidth:  uint32(width),\n\t\tHeight: uint32(height),\n\t}\n\treturn resolution\n}\n\nfunc randomScreenPanel() pb.Screen_Panel {\n\tif rand.Intn(2) == 1 {\n\t\treturn pb.Screen_IPS\n\t}\n\treturn pb.Screen_OLED\n}\n\nfunc randomCPUBrand() string {\n\treturn randomStringFromSet(\"Intel\", \"AMD\")\n}\n\nfunc randomCPUName(brand string) string {\n\tif brand == \"Intel\" {\n\t\treturn randomStringFromSet(\n\t\t\t\"Xeon E-2286M\",\n\t\t\t\"Core i9-9980HK\",\n\t\t\t\"Core i7-9750H\",\n\t\t\t\"Core i5-9400F\",\n\t\t\t\"Core i3-1005G1\",\n\t\t)\n\t}\n\n\treturn randomStringFromSet(\n\t\t\"Ryzen 7 PRO 2700U\",\n\t\t\"Ryzen 5 PRO 3500U\",\n\t\t\"Ryzen 3 PRO 3200GE\",\n\t)\n}\n\nfunc randomGPUBrand() string {\n\treturn randomStringFromSet(\"Nvidia\", \"AMD\")\n}\n\nfunc randomGPUName(brand string) string {\n\tif brand == \"Nvidia\" {\n\t\treturn randomStringFromSet(\n\t\t\t\"RTX 2060\",\n\t\t\t\"RTX 2070\",\n\t\t\t\"GTX 1660-Ti\",\n\t\t\t\"GTX 1070\",\n\t\t)\n\t}\n\n\treturn randomStringFromSet(\n\t\t\"RX 590\",\n\t\t\"RX 580\",\n\t\t\"RX 5700-XT\",\n\t\t\"RX Vega-56\",\n\t)\n}\n\nfunc randomLaptopBrand() string {\n\treturn randomStringFromSet(\"Apple\", \"Dell\", \"Lenovo\")\n}\n\nfunc randomLaptopName(brand string) string {\n\tswitch brand {\n\tcase \"Apple\":\n\t\treturn randomStringFromSet(\"Macbook Air\", \"Macbook Pro\")\n\tcase \"Dell\":\n\t\treturn randomStringFromSet(\"Latitude\", \"Vostro\", \"XPS\", \"Alienware\")\n\tdefault:\n\t\treturn randomStringFromSet(\"Thinkpad X1\", \"Thinkpad P1\", \"Thinkpad P53\")\n\t}\n}\n"
  },
  {
    "path": "serializer/file.go",
    "content": "package serializer\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// WriteProtobufToJSONFile writes protocol buffer message to JSON file\nfunc WriteProtobufToJSONFile(message proto.Message, filename string) error {\n\tdata, err := ProtobufToJSON(message)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot marshal proto message to JSON: %w\", err)\n\t}\n\n\terr = ioutil.WriteFile(filename, []byte(data), 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot write JSON data to file: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// WriteProtobufToBinaryFile writes protocol buffer message to binary file\nfunc WriteProtobufToBinaryFile(message proto.Message, filename string) error {\n\tdata, err := proto.Marshal(message)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot marshal proto message to binary: %w\", err)\n\t}\n\n\terr = ioutil.WriteFile(filename, data, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot write binary data to file: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// ReadProtobufFromBinaryFile reads protocol buffer message from binary file\nfunc ReadProtobufFromBinaryFile(filename string, message proto.Message) error {\n\tdata, err := ioutil.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot read binary data from file: %w\", err)\n\t}\n\n\terr = proto.Unmarshal(data, message)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot unmarshal binary to proto message: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "serializer/file_test.go",
    "content": "package serializer_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/protobuf/proto\"\n\t\"github.com/stretchr/testify/require\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"gitlab.com/techschool/pcbook/sample\"\n\t\"gitlab.com/techschool/pcbook/serializer\"\n)\n\nfunc TestFileSerializer(t *testing.T) {\n\tt.Parallel()\n\n\tbinaryFile := \"../tmp/laptop.bin\"\n\tjsonFile := \"../tmp/laptop.json\"\n\n\tlaptop1 := sample.NewLaptop()\n\n\terr := serializer.WriteProtobufToBinaryFile(laptop1, binaryFile)\n\trequire.NoError(t, err)\n\n\terr = serializer.WriteProtobufToJSONFile(laptop1, jsonFile)\n\trequire.NoError(t, err)\n\n\tlaptop2 := &pb.Laptop{}\n\terr = serializer.ReadProtobufFromBinaryFile(binaryFile, laptop2)\n\trequire.NoError(t, err)\n\n\trequire.True(t, proto.Equal(laptop1, laptop2))\n}\n"
  },
  {
    "path": "serializer/json.go",
    "content": "package serializer\n\nimport (\n\t\"github.com/golang/protobuf/jsonpb\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// ProtobufToJSON converts protocol buffer message to JSON string\nfunc ProtobufToJSON(message proto.Message) (string, error) {\n\tmarshaler := jsonpb.Marshaler{\n\t\tEnumsAsInts:  false,\n\t\tEmitDefaults: true,\n\t\tIndent:       \"  \",\n\t\tOrigName:     true,\n\t}\n\n\treturn marshaler.MarshalToString(message)\n}\n\n// JSONToProtobufMessage converts JSON string to protocol buffer message\nfunc JSONToProtobufMessage(data string, message proto.Message) error {\n\treturn jsonpb.UnmarshalString(data, message)\n}\n"
  },
  {
    "path": "service/auth_interceptor.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// AuthInterceptor is a server interceptor for authentication and authorization\ntype AuthInterceptor struct {\n\tjwtManager      *JWTManager\n\taccessibleRoles map[string][]string\n}\n\n// NewAuthInterceptor returns a new auth interceptor\nfunc NewAuthInterceptor(jwtManager *JWTManager, accessibleRoles map[string][]string) *AuthInterceptor {\n\treturn &AuthInterceptor{jwtManager, accessibleRoles}\n}\n\n// Unary returns a server interceptor function to authenticate and authorize unary RPC\nfunc (interceptor *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {\n\treturn func(\n\t\tctx context.Context,\n\t\treq interface{},\n\t\tinfo *grpc.UnaryServerInfo,\n\t\thandler grpc.UnaryHandler,\n\t) (interface{}, error) {\n\t\tlog.Println(\"--> unary interceptor: \", info.FullMethod)\n\n\t\terr := interceptor.authorize(ctx, info.FullMethod)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn handler(ctx, req)\n\t}\n}\n\n// Stream returns a server interceptor function to authenticate and authorize stream RPC\nfunc (interceptor *AuthInterceptor) Stream() grpc.StreamServerInterceptor {\n\treturn func(\n\t\tsrv interface{},\n\t\tstream grpc.ServerStream,\n\t\tinfo *grpc.StreamServerInfo,\n\t\thandler grpc.StreamHandler,\n\t) error {\n\t\tlog.Println(\"--> stream interceptor: \", info.FullMethod)\n\n\t\terr := interceptor.authorize(stream.Context(), info.FullMethod)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn handler(srv, stream)\n\t}\n}\n\nfunc (interceptor *AuthInterceptor) authorize(ctx context.Context, method string) error {\n\taccessibleRoles, ok := interceptor.accessibleRoles[method]\n\tif !ok {\n\t\t// everyone can access\n\t\treturn nil\n\t}\n\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn status.Errorf(codes.Unauthenticated, \"metadata is not provided\")\n\t}\n\n\tvalues := md[\"authorization\"]\n\tif len(values) == 0 {\n\t\treturn status.Errorf(codes.Unauthenticated, \"authorization token is not provided\")\n\t}\n\n\taccessToken := values[0]\n\tclaims, err := interceptor.jwtManager.Verify(accessToken)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Unauthenticated, \"access token is invalid: %v\", err)\n\t}\n\n\tfor _, role := range accessibleRoles {\n\t\tif role == claims.Role {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn status.Error(codes.PermissionDenied, \"no permission to access this RPC\")\n}\n"
  },
  {
    "path": "service/auth_server.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// AuthServer is the server for authentication\ntype AuthServer struct {\n\tpb.UnimplementedAuthServiceServer\n\tuserStore  UserStore\n\tjwtManager *JWTManager\n}\n\n// NewAuthServer returns a new auth server\nfunc NewAuthServer(userStore UserStore, jwtManager *JWTManager) pb.AuthServiceServer {\n\treturn &AuthServer{userStore: userStore, jwtManager: jwtManager}\n}\n\n// Login is a unary RPC to login user\nfunc (server *AuthServer) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) {\n\tuser, err := server.userStore.Find(req.GetUsername())\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"cannot find user: %v\", err)\n\t}\n\n\tif user == nil || !user.IsCorrectPassword(req.GetPassword()) {\n\t\treturn nil, status.Errorf(codes.NotFound, \"incorrect username/password\")\n\t}\n\n\ttoken, err := server.jwtManager.Generate(user)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.Internal, \"cannot generate access token\")\n\t}\n\n\tres := &pb.LoginResponse{AccessToken: token}\n\treturn res, nil\n}\n"
  },
  {
    "path": "service/image_store.go",
    "content": "package service\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/google/uuid\"\n)\n\n// ImageStore is an interface to store laptop images\ntype ImageStore interface {\n\t// Save saves a new laptop image to the store\n\tSave(laptopID string, imageType string, imageData bytes.Buffer) (string, error)\n}\n\n// DiskImageStore stores image on disk, and its info on memory\ntype DiskImageStore struct {\n\tmutex       sync.RWMutex\n\timageFolder string\n\timages      map[string]*ImageInfo\n}\n\n// ImageInfo contains information of the laptop image\ntype ImageInfo struct {\n\tLaptopID string\n\tType     string\n\tPath     string\n}\n\n// NewDiskImageStore returns a new DiskImageStore\nfunc NewDiskImageStore(imageFolder string) *DiskImageStore {\n\treturn &DiskImageStore{\n\t\timageFolder: imageFolder,\n\t\timages:      make(map[string]*ImageInfo),\n\t}\n}\n\n// Save adds a new image to a laptop\nfunc (store *DiskImageStore) Save(\n\tlaptopID string,\n\timageType string,\n\timageData bytes.Buffer,\n) (string, error) {\n\timageID, err := uuid.NewRandom()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot generate image id: %w\", err)\n\t}\n\n\timagePath := fmt.Sprintf(\"%s/%s%s\", store.imageFolder, imageID, imageType)\n\n\tfile, err := os.Create(imagePath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot create image file: %w\", err)\n\t}\n\n\t_, err = imageData.WriteTo(file)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot write image to file: %w\", err)\n\t}\n\n\tstore.mutex.Lock()\n\tdefer store.mutex.Unlock()\n\n\tstore.images[imageID.String()] = &ImageInfo{\n\t\tLaptopID: laptopID,\n\t\tType:     imageType,\n\t\tPath:     imagePath,\n\t}\n\n\treturn imageID.String(), nil\n}\n"
  },
  {
    "path": "service/jwt_manager.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dgrijalva/jwt-go\"\n)\n\n// JWTManager is a JSON web token manager\ntype JWTManager struct {\n\tsecretKey     string\n\ttokenDuration time.Duration\n}\n\n// UserClaims is a custom JWT claims that contains some user's information\ntype UserClaims struct {\n\tjwt.StandardClaims\n\tUsername string `json:\"username\"`\n\tRole     string `json:\"role\"`\n}\n\n// NewJWTManager returns a new JWT manager\nfunc NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {\n\treturn &JWTManager{secretKey, tokenDuration}\n}\n\n// Generate generates and signs a new token for a user\nfunc (manager *JWTManager) Generate(user *User) (string, error) {\n\tclaims := UserClaims{\n\t\tStandardClaims: jwt.StandardClaims{\n\t\t\tExpiresAt: time.Now().Add(manager.tokenDuration).Unix(),\n\t\t},\n\t\tUsername: user.Username,\n\t\tRole:     user.Role,\n\t}\n\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\treturn token.SignedString([]byte(manager.secretKey))\n}\n\n// Verify verifies the access token string and return a user claim if the token is valid\nfunc (manager *JWTManager) Verify(accessToken string) (*UserClaims, error) {\n\ttoken, err := jwt.ParseWithClaims(\n\t\taccessToken,\n\t\t&UserClaims{},\n\t\tfunc(token *jwt.Token) (interface{}, error) {\n\t\t\t_, ok := token.Method.(*jwt.SigningMethodHMAC)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected token signing method\")\n\t\t\t}\n\n\t\t\treturn []byte(manager.secretKey), nil\n\t\t},\n\t)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid token: %w\", err)\n\t}\n\n\tclaims, ok := token.Claims.(*UserClaims)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"invalid token claims\")\n\t}\n\n\treturn claims, nil\n}\n"
  },
  {
    "path": "service/laptop_client_test.go",
    "content": "package service_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"gitlab.com/techschool/pcbook/sample\"\n\t\"gitlab.com/techschool/pcbook/serializer\"\n\t\"gitlab.com/techschool/pcbook/service\"\n\t\"google.golang.org/grpc\"\n)\n\nfunc TestClientCreateLaptop(t *testing.T) {\n\tt.Parallel()\n\n\tlaptopStore := service.NewInMemoryLaptopStore()\n\tserverAddress := startTestLaptopServer(t, laptopStore, nil, nil)\n\tlaptopClient := newTestLaptopClient(t, serverAddress)\n\n\tlaptop := sample.NewLaptop()\n\texpectedID := laptop.Id\n\treq := &pb.CreateLaptopRequest{\n\t\tLaptop: laptop,\n\t}\n\n\tres, err := laptopClient.CreateLaptop(context.Background(), req)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, res)\n\trequire.Equal(t, expectedID, res.Id)\n\n\t// check that the laptop is saved to the store\n\tother, err := laptopStore.Find(res.Id)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, other)\n\n\t// check that the saved laptop is the same as the one we send\n\trequireSameLaptop(t, laptop, other)\n}\n\nfunc TestClientSearchLaptop(t *testing.T) {\n\tt.Parallel()\n\n\tfilter := &pb.Filter{\n\t\tMaxPriceUsd: 2000,\n\t\tMinCpuCores: 4,\n\t\tMinCpuGhz:   2.2,\n\t\tMinRam:      &pb.Memory{Value: 8, Unit: pb.Memory_GIGABYTE},\n\t}\n\n\tlaptopStore := service.NewInMemoryLaptopStore()\n\texpectedIDs := make(map[string]bool)\n\n\tfor i := 0; i < 6; i++ {\n\t\tlaptop := sample.NewLaptop()\n\n\t\tswitch i {\n\t\tcase 0:\n\t\t\tlaptop.PriceUsd = 2500\n\t\tcase 1:\n\t\t\tlaptop.Cpu.NumberCores = 2\n\t\tcase 2:\n\t\t\tlaptop.Cpu.MinGhz = 2.0\n\t\tcase 3:\n\t\t\tlaptop.Ram = &pb.Memory{Value: 4096, Unit: pb.Memory_MEGABYTE}\n\t\tcase 4:\n\t\t\tlaptop.PriceUsd = 1999\n\t\t\tlaptop.Cpu.NumberCores = 4\n\t\t\tlaptop.Cpu.MinGhz = 2.5\n\t\t\tlaptop.Cpu.MaxGhz = laptop.Cpu.MinGhz + 2.0\n\t\t\tlaptop.Ram = &pb.Memory{Value: 16, Unit: pb.Memory_GIGABYTE}\n\t\t\texpectedIDs[laptop.Id] = true\n\t\tcase 5:\n\t\t\tlaptop.PriceUsd = 2000\n\t\t\tlaptop.Cpu.NumberCores = 6\n\t\t\tlaptop.Cpu.MinGhz = 2.8\n\t\t\tlaptop.Cpu.MaxGhz = laptop.Cpu.MinGhz + 2.0\n\t\t\tlaptop.Ram = &pb.Memory{Value: 64, Unit: pb.Memory_GIGABYTE}\n\t\t\texpectedIDs[laptop.Id] = true\n\t\t}\n\n\t\terr := laptopStore.Save(laptop)\n\t\trequire.NoError(t, err)\n\t}\n\n\tserverAddress := startTestLaptopServer(t, laptopStore, nil, nil)\n\tlaptopClient := newTestLaptopClient(t, serverAddress)\n\n\treq := &pb.SearchLaptopRequest{Filter: filter}\n\tstream, err := laptopClient.SearchLaptop(context.Background(), req)\n\trequire.NoError(t, err)\n\n\tfound := 0\n\tfor {\n\t\tres, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, expectedIDs, res.GetLaptop().GetId())\n\n\t\tfound += 1\n\t}\n\n\trequire.Equal(t, len(expectedIDs), found)\n}\n\nfunc TestClientUploadImage(t *testing.T) {\n\tt.Parallel()\n\n\ttestImageFolder := \"../tmp\"\n\n\tlaptopStore := service.NewInMemoryLaptopStore()\n\timageStore := service.NewDiskImageStore(testImageFolder)\n\n\tlaptop := sample.NewLaptop()\n\terr := laptopStore.Save(laptop)\n\trequire.NoError(t, err)\n\n\tserverAddress := startTestLaptopServer(t, laptopStore, imageStore, nil)\n\tlaptopClient := newTestLaptopClient(t, serverAddress)\n\n\timagePath := fmt.Sprintf(\"%s/laptop.jpg\", testImageFolder)\n\tfile, err := os.Open(imagePath)\n\trequire.NoError(t, err)\n\tdefer file.Close()\n\n\tstream, err := laptopClient.UploadImage(context.Background())\n\trequire.NoError(t, err)\n\n\timageType := filepath.Ext(imagePath)\n\treq := &pb.UploadImageRequest{\n\t\tData: &pb.UploadImageRequest_Info{\n\t\t\tInfo: &pb.ImageInfo{\n\t\t\t\tLaptopId:  laptop.GetId(),\n\t\t\t\tImageType: imageType,\n\t\t\t},\n\t\t},\n\t}\n\n\terr = stream.Send(req)\n\trequire.NoError(t, err)\n\n\treader := bufio.NewReader(file)\n\tbuffer := make([]byte, 1024)\n\tsize := 0\n\n\tfor {\n\t\tn, err := reader.Read(buffer)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\tsize += n\n\n\t\treq := &pb.UploadImageRequest{\n\t\t\tData: &pb.UploadImageRequest_ChunkData{\n\t\t\t\tChunkData: buffer[:n],\n\t\t\t},\n\t\t}\n\n\t\terr = stream.Send(req)\n\t\trequire.NoError(t, err)\n\t}\n\n\tres, err := stream.CloseAndRecv()\n\trequire.NoError(t, err)\n\trequire.NotZero(t, res.GetId())\n\trequire.EqualValues(t, size, res.GetSize())\n\n\tsavedImagePath := fmt.Sprintf(\"%s/%s%s\", testImageFolder, res.GetId(), imageType)\n\trequire.FileExists(t, savedImagePath)\n\trequire.NoError(t, os.Remove(savedImagePath))\n}\n\nfunc TestClientRateLaptop(t *testing.T) {\n\tt.Parallel()\n\n\tlaptopStore := service.NewInMemoryLaptopStore()\n\tratingStore := service.NewInMemoryRatingStore()\n\n\tlaptop := sample.NewLaptop()\n\terr := laptopStore.Save(laptop)\n\trequire.NoError(t, err)\n\n\tserverAddress := startTestLaptopServer(t, laptopStore, nil, ratingStore)\n\tlaptopClient := newTestLaptopClient(t, serverAddress)\n\n\tstream, err := laptopClient.RateLaptop(context.Background())\n\trequire.NoError(t, err)\n\n\tscores := []float64{8, 7.5, 10}\n\taverages := []float64{8, 7.75, 8.5}\n\n\tn := len(scores)\n\tfor i := 0; i < n; i++ {\n\t\treq := &pb.RateLaptopRequest{\n\t\t\tLaptopId: laptop.GetId(),\n\t\t\tScore:    scores[i],\n\t\t}\n\n\t\terr := stream.Send(req)\n\t\trequire.NoError(t, err)\n\t}\n\n\terr = stream.CloseSend()\n\trequire.NoError(t, err)\n\n\tfor idx := 0; ; idx++ {\n\t\tres, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\trequire.Equal(t, n, idx)\n\t\t\treturn\n\t\t}\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, laptop.GetId(), res.GetLaptopId())\n\t\trequire.Equal(t, uint32(idx+1), res.GetRatedCount())\n\t\trequire.Equal(t, averages[idx], res.GetAverageScore())\n\t}\n}\n\nfunc startTestLaptopServer(t *testing.T, laptopStore service.LaptopStore, imageStore service.ImageStore, ratingStore service.RatingStore) string {\n\tlaptopServer := service.NewLaptopServer(laptopStore, imageStore, ratingStore)\n\n\tgrpcServer := grpc.NewServer()\n\tpb.RegisterLaptopServiceServer(grpcServer, laptopServer)\n\n\tlistener, err := net.Listen(\"tcp\", \":0\") // random available port\n\trequire.NoError(t, err)\n\n\tgo grpcServer.Serve(listener)\n\n\treturn listener.Addr().String()\n}\n\nfunc newTestLaptopClient(t *testing.T, serverAddress string) pb.LaptopServiceClient {\n\tconn, err := grpc.Dial(serverAddress, grpc.WithInsecure())\n\trequire.NoError(t, err)\n\treturn pb.NewLaptopServiceClient(conn)\n}\n\nfunc requireSameLaptop(t *testing.T, laptop1 *pb.Laptop, laptop2 *pb.Laptop) {\n\tjson1, err := serializer.ProtobufToJSON(laptop1)\n\trequire.NoError(t, err)\n\n\tjson2, err := serializer.ProtobufToJSON(laptop2)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, json1, json2)\n}\n"
  },
  {
    "path": "service/laptop_server.go",
    "content": "package service\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"log\"\n\n\t\"github.com/google/uuid\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nconst maxImageSize = 1 << 20\n\n// LaptopServer is the server that provides laptop services\ntype LaptopServer struct {\n\tpb.UnimplementedLaptopServiceServer\n\tlaptopStore LaptopStore\n\timageStore  ImageStore\n\tratingStore RatingStore\n}\n\n// NewLaptopServer returns a new LaptopServer\nfunc NewLaptopServer(laptopStore LaptopStore, imageStore ImageStore, ratingStore RatingStore) *LaptopServer {\n\treturn &LaptopServer{\n\t\tlaptopStore: laptopStore,\n\t\timageStore:  imageStore,\n\t\tratingStore: ratingStore,\n\t}\n}\n\n// CreateLaptop is a unary RPC to create a new laptop\nfunc (server *LaptopServer) CreateLaptop(\n\tctx context.Context,\n\treq *pb.CreateLaptopRequest,\n) (*pb.CreateLaptopResponse, error) {\n\tlaptop := req.GetLaptop()\n\tlog.Printf(\"receive a create-laptop request with id: %s\", laptop.Id)\n\n\tif len(laptop.Id) > 0 {\n\t\t// check if it's a valid UUID\n\t\t_, err := uuid.Parse(laptop.Id)\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.InvalidArgument, \"laptop ID is not a valid UUID: %v\", err)\n\t\t}\n\t} else {\n\t\tid, err := uuid.NewRandom()\n\t\tif err != nil {\n\t\t\treturn nil, status.Errorf(codes.Internal, \"cannot generate a new laptop ID: %v\", err)\n\t\t}\n\t\tlaptop.Id = id.String()\n\t}\n\n\t// some heavy processing\n\t// time.Sleep(6 * time.Second)\n\n\tif err := contextError(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// save the laptop to store\n\terr := server.laptopStore.Save(laptop)\n\tif err != nil {\n\t\tcode := codes.Internal\n\t\tif errors.Is(err, ErrAlreadyExists) {\n\t\t\tcode = codes.AlreadyExists\n\t\t}\n\n\t\treturn nil, status.Errorf(code, \"cannot save laptop to the store: %v\", err)\n\t}\n\n\tlog.Printf(\"saved laptop with id: %s\", laptop.Id)\n\n\tres := &pb.CreateLaptopResponse{\n\t\tId: laptop.Id,\n\t}\n\treturn res, nil\n}\n\n// SearchLaptop is a server-streaming RPC to search for laptops\nfunc (server *LaptopServer) SearchLaptop(\n\treq *pb.SearchLaptopRequest,\n\tstream pb.LaptopService_SearchLaptopServer,\n) error {\n\tfilter := req.GetFilter()\n\tlog.Printf(\"receive a search-laptop request with filter: %v\", filter)\n\n\terr := server.laptopStore.Search(\n\t\tstream.Context(),\n\t\tfilter,\n\t\tfunc(laptop *pb.Laptop) error {\n\t\t\tres := &pb.SearchLaptopResponse{Laptop: laptop}\n\t\t\terr := stream.Send(res)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlog.Printf(\"sent laptop with id: %s\", laptop.GetId())\n\t\t\treturn nil\n\t\t},\n\t)\n\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"unexpected error: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// UploadImage is a client-streaming RPC to upload a laptop image\nfunc (server *LaptopServer) UploadImage(stream pb.LaptopService_UploadImageServer) error {\n\treq, err := stream.Recv()\n\tif err != nil {\n\t\treturn logError(status.Errorf(codes.Unknown, \"cannot receive image info\"))\n\t}\n\n\tlaptopID := req.GetInfo().GetLaptopId()\n\timageType := req.GetInfo().GetImageType()\n\tlog.Printf(\"receive an upload-image request for laptop %s with image type %s\", laptopID, imageType)\n\n\tlaptop, err := server.laptopStore.Find(laptopID)\n\tif err != nil {\n\t\treturn logError(status.Errorf(codes.Internal, \"cannot find laptop: %v\", err))\n\t}\n\tif laptop == nil {\n\t\treturn logError(status.Errorf(codes.InvalidArgument, \"laptop id %s doesn't exist\", laptopID))\n\t}\n\n\timageData := bytes.Buffer{}\n\timageSize := 0\n\n\tfor {\n\t\terr := contextError(stream.Context())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlog.Print(\"waiting to receive more data\")\n\n\t\treq, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\tlog.Print(\"no more data\")\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Unknown, \"cannot receive chunk data: %v\", err))\n\t\t}\n\n\t\tchunk := req.GetChunkData()\n\t\tsize := len(chunk)\n\n\t\tlog.Printf(\"received a chunk with size: %d\", size)\n\n\t\timageSize += size\n\t\tif imageSize > maxImageSize {\n\t\t\treturn logError(status.Errorf(codes.InvalidArgument, \"image is too large: %d > %d\", imageSize, maxImageSize))\n\t\t}\n\n\t\t// write slowly\n\t\t// time.Sleep(time.Second)\n\n\t\t_, err = imageData.Write(chunk)\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Internal, \"cannot write chunk data: %v\", err))\n\t\t}\n\t}\n\n\timageID, err := server.imageStore.Save(laptopID, imageType, imageData)\n\tif err != nil {\n\t\treturn logError(status.Errorf(codes.Internal, \"cannot save image to the store: %v\", err))\n\t}\n\n\tres := &pb.UploadImageResponse{\n\t\tId:   imageID,\n\t\tSize: uint32(imageSize),\n\t}\n\n\terr = stream.SendAndClose(res)\n\tif err != nil {\n\t\treturn logError(status.Errorf(codes.Unknown, \"cannot send response: %v\", err))\n\t}\n\n\tlog.Printf(\"saved image with id: %s, size: %d\", imageID, imageSize)\n\treturn nil\n}\n\n// RateLaptop is a bidirectional-streaming RPC that allows client to rate a stream of laptops\n// with a score, and returns a stream of average score for each of them\nfunc (server *LaptopServer) RateLaptop(stream pb.LaptopService_RateLaptopServer) error {\n\tfor {\n\t\terr := contextError(stream.Context())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treq, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\tlog.Print(\"no more data\")\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Unknown, \"cannot receive stream request: %v\", err))\n\t\t}\n\n\t\tlaptopID := req.GetLaptopId()\n\t\tscore := req.GetScore()\n\n\t\tlog.Printf(\"received a rate-laptop request: id = %s, score = %.2f\", laptopID, score)\n\n\t\tfound, err := server.laptopStore.Find(laptopID)\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Internal, \"cannot find laptop: %v\", err))\n\t\t}\n\t\tif found == nil {\n\t\t\treturn logError(status.Errorf(codes.NotFound, \"laptopID %s is not found\", laptopID))\n\t\t}\n\n\t\trating, err := server.ratingStore.Add(laptopID, score)\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Internal, \"cannot add rating to the store: %v\", err))\n\t\t}\n\n\t\tres := &pb.RateLaptopResponse{\n\t\t\tLaptopId:     laptopID,\n\t\t\tRatedCount:   rating.Count,\n\t\t\tAverageScore: rating.Sum / float64(rating.Count),\n\t\t}\n\n\t\terr = stream.Send(res)\n\t\tif err != nil {\n\t\t\treturn logError(status.Errorf(codes.Unknown, \"cannot send stream response: %v\", err))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc contextError(ctx context.Context) error {\n\tswitch ctx.Err() {\n\tcase context.Canceled:\n\t\treturn logError(status.Error(codes.Canceled, \"request is canceled\"))\n\tcase context.DeadlineExceeded:\n\t\treturn logError(status.Error(codes.DeadlineExceeded, \"deadline is exceeded\"))\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logError(err error) error {\n\tif err != nil {\n\t\tlog.Print(err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "service/laptop_server_test.go",
    "content": "package service_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n\t\"gitlab.com/techschool/pcbook/sample\"\n\t\"gitlab.com/techschool/pcbook/service\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc TestServerCreateLaptop(t *testing.T) {\n\tt.Parallel()\n\n\tlaptopNoID := sample.NewLaptop()\n\tlaptopNoID.Id = \"\"\n\n\tlaptopInvalidID := sample.NewLaptop()\n\tlaptopInvalidID.Id = \"invalid-uuid\"\n\n\tlaptopDuplicateID := sample.NewLaptop()\n\tstoreDuplicateID := service.NewInMemoryLaptopStore()\n\terr := storeDuplicateID.Save(laptopDuplicateID)\n\trequire.Nil(t, err)\n\n\ttestCases := []struct {\n\t\tname   string\n\t\tlaptop *pb.Laptop\n\t\tstore  service.LaptopStore\n\t\tcode   codes.Code\n\t}{\n\t\t{\n\t\t\tname:   \"success_with_id\",\n\t\t\tlaptop: sample.NewLaptop(),\n\t\t\tstore:  service.NewInMemoryLaptopStore(),\n\t\t\tcode:   codes.OK,\n\t\t},\n\t\t{\n\t\t\tname:   \"success_no_id\",\n\t\t\tlaptop: laptopNoID,\n\t\t\tstore:  service.NewInMemoryLaptopStore(),\n\t\t\tcode:   codes.OK,\n\t\t},\n\t\t{\n\t\t\tname:   \"failure_invalid_id\",\n\t\t\tlaptop: laptopInvalidID,\n\t\t\tstore:  service.NewInMemoryLaptopStore(),\n\t\t\tcode:   codes.InvalidArgument,\n\t\t},\n\t\t{\n\t\t\tname:   \"failure_duplicate_id\",\n\t\t\tlaptop: laptopDuplicateID,\n\t\t\tstore:  storeDuplicateID,\n\t\t\tcode:   codes.AlreadyExists,\n\t\t},\n\t}\n\n\tfor i := range testCases {\n\t\ttc := testCases[i]\n\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\treq := &pb.CreateLaptopRequest{\n\t\t\t\tLaptop: tc.laptop,\n\t\t\t}\n\n\t\t\tserver := service.NewLaptopServer(tc.store, nil, nil)\n\t\t\tres, err := server.CreateLaptop(context.Background(), req)\n\t\t\tif tc.code == codes.OK {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, res)\n\t\t\t\trequire.NotEmpty(t, res.Id)\n\t\t\t\tif len(tc.laptop.Id) > 0 {\n\t\t\t\t\trequire.Equal(t, tc.laptop.Id, res.Id)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Nil(t, res)\n\t\t\t\tst, ok := status.FromError(err)\n\t\t\t\trequire.True(t, ok)\n\t\t\t\trequire.Equal(t, tc.code, st.Code())\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "service/laptop_store.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n\n\t\"github.com/jinzhu/copier\"\n\t\"gitlab.com/techschool/pcbook/pb\"\n)\n\n// ErrAlreadyExists is returned when a record with the same ID already exists in the store\nvar ErrAlreadyExists = errors.New(\"record already exists\")\n\n// LaptopStore is an interface to store laptop\ntype LaptopStore interface {\n\t// Save saves the laptop to the store\n\tSave(laptop *pb.Laptop) error\n\t// Find finds a laptop by ID\n\tFind(id string) (*pb.Laptop, error)\n\t// Search searches for laptops with filter, returns one by one via the found function\n\tSearch(ctx context.Context, filter *pb.Filter, found func(laptop *pb.Laptop) error) error\n}\n\n// InMemoryLaptopStore stores laptop in memory\ntype InMemoryLaptopStore struct {\n\tmutex sync.RWMutex\n\tdata  map[string]*pb.Laptop\n}\n\n// NewInMemoryLaptopStore returns a new InMemoryLaptopStore\nfunc NewInMemoryLaptopStore() *InMemoryLaptopStore {\n\treturn &InMemoryLaptopStore{\n\t\tdata: make(map[string]*pb.Laptop),\n\t}\n}\n\n// Save saves the laptop to the store\nfunc (store *InMemoryLaptopStore) Save(laptop *pb.Laptop) error {\n\tstore.mutex.Lock()\n\tdefer store.mutex.Unlock()\n\n\tif store.data[laptop.Id] != nil {\n\t\treturn ErrAlreadyExists\n\t}\n\n\tother, err := deepCopy(laptop)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstore.data[other.Id] = other\n\treturn nil\n}\n\n// Find finds a laptop by ID\nfunc (store *InMemoryLaptopStore) Find(id string) (*pb.Laptop, error) {\n\tstore.mutex.RLock()\n\tdefer store.mutex.RUnlock()\n\n\tlaptop := store.data[id]\n\tif laptop == nil {\n\t\treturn nil, nil\n\t}\n\n\treturn deepCopy(laptop)\n}\n\n// Search searches for laptops with filter, returns one by one via the found function\nfunc (store *InMemoryLaptopStore) Search(\n\tctx context.Context,\n\tfilter *pb.Filter,\n\tfound func(laptop *pb.Laptop) error,\n) error {\n\tstore.mutex.RLock()\n\tdefer store.mutex.RUnlock()\n\n\tfor _, laptop := range store.data {\n\t\tif ctx.Err() == context.Canceled || ctx.Err() == context.DeadlineExceeded {\n\t\t\tlog.Print(\"context is cancelled\")\n\t\t\treturn nil\n\t\t}\n\n\t\t// time.Sleep(time.Second)\n\t\t// log.Print(\"checking laptop id: \", laptop.GetId())\n\n\t\tif isQualified(filter, laptop) {\n\t\t\tother, err := deepCopy(laptop)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = found(other)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc isQualified(filter *pb.Filter, laptop *pb.Laptop) bool {\n\tif laptop.GetPriceUsd() > filter.GetMaxPriceUsd() {\n\t\treturn false\n\t}\n\n\tif laptop.GetCpu().GetNumberCores() < filter.GetMinCpuCores() {\n\t\treturn false\n\t}\n\n\tif laptop.GetCpu().GetMinGhz() < filter.GetMinCpuGhz() {\n\t\treturn false\n\t}\n\n\tif toBit(laptop.GetRam()) < toBit(filter.GetMinRam()) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc toBit(memory *pb.Memory) uint64 {\n\tvalue := memory.GetValue()\n\n\tswitch memory.GetUnit() {\n\tcase pb.Memory_BIT:\n\t\treturn value\n\tcase pb.Memory_BYTE:\n\t\treturn value << 3 // 8 = 2^3\n\tcase pb.Memory_KILOBYTE:\n\t\treturn value << 13 // 1024 * 8 = 2^10 * 2^3 = 2^13\n\tcase pb.Memory_MEGABYTE:\n\t\treturn value << 23\n\tcase pb.Memory_GIGABYTE:\n\t\treturn value << 33\n\tcase pb.Memory_TERABYTE:\n\t\treturn value << 43\n\tdefault:\n\t\treturn 0\n\t}\n}\n\nfunc deepCopy(laptop *pb.Laptop) (*pb.Laptop, error) {\n\tother := &pb.Laptop{}\n\n\terr := copier.Copy(other, laptop)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot copy laptop data: %w\", err)\n\t}\n\n\treturn other, nil\n}\n"
  },
  {
    "path": "service/rating_store.go",
    "content": "package service\n\nimport \"sync\"\n\n// RatingStore is an interface to store laptop ratings\ntype RatingStore interface {\n\t// Add adds a new laptop score to the store and returns its rating\n\tAdd(laptopID string, score float64) (*Rating, error)\n}\n\n// Rating contains the rating information of a laptop\ntype Rating struct {\n\tCount uint32\n\tSum   float64\n}\n\n// InMemoryRatingStore stores laptop ratings in memory\ntype InMemoryRatingStore struct {\n\tmutex  sync.RWMutex\n\trating map[string]*Rating\n}\n\n// NewInMemoryRatingStore returns a new InMemoryRatingStore\nfunc NewInMemoryRatingStore() *InMemoryRatingStore {\n\treturn &InMemoryRatingStore{\n\t\trating: make(map[string]*Rating),\n\t}\n}\n\n// Add adds a new laptop score to the store and returns its rating\nfunc (store *InMemoryRatingStore) Add(laptopID string, score float64) (*Rating, error) {\n\tstore.mutex.Lock()\n\tdefer store.mutex.Unlock()\n\n\trating := store.rating[laptopID]\n\tif rating == nil {\n\t\trating = &Rating{\n\t\t\tCount: 1,\n\t\t\tSum:   score,\n\t\t}\n\t} else {\n\t\trating.Count++\n\t\trating.Sum += score\n\t}\n\n\tstore.rating[laptopID] = rating\n\treturn rating, nil\n}\n"
  },
  {
    "path": "service/user.go",
    "content": "package service\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\n// User contains user's information\ntype User struct {\n\tUsername       string\n\tHashedPassword string\n\tRole           string\n}\n\n// NewUser returns a new user\nfunc NewUser(username string, password string, role string) (*User, error) {\n\thashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot hash password: %w\", err)\n\t}\n\n\tuser := &User{\n\t\tUsername:       username,\n\t\tHashedPassword: string(hashedPassword),\n\t\tRole:           role,\n\t}\n\n\treturn user, nil\n}\n\n// IsCorrectPassword checks if the provided password is correct or not\nfunc (user *User) IsCorrectPassword(password string) bool {\n\terr := bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(password))\n\treturn err == nil\n}\n\n// Clone returns a clone of this user\nfunc (user *User) Clone() *User {\n\treturn &User{\n\t\tUsername:       user.Username,\n\t\tHashedPassword: user.HashedPassword,\n\t\tRole:           user.Role,\n\t}\n}\n"
  },
  {
    "path": "service/user_store.go",
    "content": "package service\n\nimport \"sync\"\n\n// UserStore is an interface to store users\ntype UserStore interface {\n\t// Save saves a user to the store\n\tSave(user *User) error\n\t// Find finds a user by username\n\tFind(username string) (*User, error)\n}\n\n// InMemoryUserStore stores users in memory\ntype InMemoryUserStore struct {\n\tmutex sync.RWMutex\n\tusers map[string]*User\n}\n\n// NewInMemoryUserStore returns a new in-memory user store\nfunc NewInMemoryUserStore() *InMemoryUserStore {\n\treturn &InMemoryUserStore{\n\t\tusers: make(map[string]*User),\n\t}\n}\n\n// Save saves a user to the store\nfunc (store *InMemoryUserStore) Save(user *User) error {\n\tstore.mutex.Lock()\n\tdefer store.mutex.Unlock()\n\n\tif store.users[user.Username] != nil {\n\t\treturn ErrAlreadyExists\n\t}\n\n\tstore.users[user.Username] = user.Clone()\n\treturn nil\n}\n\n// Find finds a user by username\nfunc (store *InMemoryUserStore) Find(username string) (*User, error) {\n\tstore.mutex.RLock()\n\tdefer store.mutex.RUnlock()\n\n\tuser := store.users[username]\n\tif user == nil {\n\t\treturn nil, nil\n\t}\n\n\treturn user.Clone(), nil\n}\n"
  },
  {
    "path": "swagger/auth_service.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"auth_service.proto\",\n    \"version\": \"version not set\"\n  },\n  \"tags\": [\n    {\n      \"name\": \"AuthService\"\n    }\n  ],\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {\n    \"/v1/auth/login\": {\n      \"post\": {\n        \"operationId\": \"AuthService_Login\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookLoginResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookLoginRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"AuthService\"\n        ]\n      }\n    }\n  },\n  \"definitions\": {\n    \"pcbookLoginRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"username\": {\n          \"type\": \"string\"\n        },\n        \"password\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"pcbookLoginResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"accessToken\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/filter_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"filter_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/keyboard_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"keyboard_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/laptop_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"laptop_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/laptop_service.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"laptop_service.proto\",\n    \"version\": \"version not set\"\n  },\n  \"tags\": [\n    {\n      \"name\": \"LaptopService\"\n    }\n  ],\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {\n    \"/v1/laptop/create\": {\n      \"post\": {\n        \"operationId\": \"LaptopService_CreateLaptop\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookCreateLaptopResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookCreateLaptopRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"LaptopService\"\n        ]\n      }\n    },\n    \"/v1/laptop/rate\": {\n      \"post\": {\n        \"operationId\": \"LaptopService_RateLaptop\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.(streaming responses)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"result\": {\n                  \"$ref\": \"#/definitions/pcbookRateLaptopResponse\"\n                },\n                \"error\": {\n                  \"$ref\": \"#/definitions/rpcStatus\"\n                }\n              },\n              \"title\": \"Stream result of pcbookRateLaptopResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"description\": \" (streaming inputs)\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookRateLaptopRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"LaptopService\"\n        ]\n      }\n    },\n    \"/v1/laptop/search\": {\n      \"get\": {\n        \"operationId\": \"LaptopService_SearchLaptop\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.(streaming responses)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"result\": {\n                  \"$ref\": \"#/definitions/pcbookSearchLaptopResponse\"\n                },\n                \"error\": {\n                  \"$ref\": \"#/definitions/rpcStatus\"\n                }\n              },\n              \"title\": \"Stream result of pcbookSearchLaptopResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"filter.maxPriceUsd\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"number\",\n            \"format\": \"double\"\n          },\n          {\n            \"name\": \"filter.minCpuCores\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"filter.minCpuGhz\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"number\",\n            \"format\": \"double\"\n          },\n          {\n            \"name\": \"filter.minRam.value\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"filter.minRam.unit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"UNKNOWN\",\n              \"BIT\",\n              \"BYTE\",\n              \"KILOBYTE\",\n              \"MEGABYTE\",\n              \"GIGABYTE\",\n              \"TERABYTE\"\n            ],\n            \"default\": \"UNKNOWN\"\n          }\n        ],\n        \"tags\": [\n          \"LaptopService\"\n        ]\n      }\n    },\n    \"/v1/laptop/upload_image\": {\n      \"post\": {\n        \"operationId\": \"LaptopService_UploadImage\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookUploadImageResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"description\": \" (streaming inputs)\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/pcbookUploadImageRequest\"\n            }\n          }\n        ],\n        \"tags\": [\n          \"LaptopService\"\n        ]\n      }\n    }\n  },\n  \"definitions\": {\n    \"KeyboardLayout\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"UNKNOWN\",\n        \"QWERTY\",\n        \"QWERTZ\",\n        \"AZERTY\"\n      ],\n      \"default\": \"UNKNOWN\"\n    },\n    \"MemoryUnit\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"UNKNOWN\",\n        \"BIT\",\n        \"BYTE\",\n        \"KILOBYTE\",\n        \"MEGABYTE\",\n        \"GIGABYTE\",\n        \"TERABYTE\"\n      ],\n      \"default\": \"UNKNOWN\"\n    },\n    \"ScreenPanel\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"UNKNOWN\",\n        \"IPS\",\n        \"OLED\"\n      ],\n      \"default\": \"UNKNOWN\"\n    },\n    \"ScreenResolution\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"width\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"height\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        }\n      }\n    },\n    \"StorageDriver\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"UNKNOWN\",\n        \"HDD\",\n        \"SSD\"\n      ],\n      \"default\": \"UNKNOWN\"\n    },\n    \"pcbookCPU\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"brand\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"numberCores\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"numberThreads\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"minGhz\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"maxGhz\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"pcbookCreateLaptopRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"laptop\": {\n          \"$ref\": \"#/definitions/pcbookLaptop\"\n        }\n      }\n    },\n    \"pcbookCreateLaptopResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"pcbookFilter\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"maxPriceUsd\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minCpuCores\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"minCpuGhz\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minRam\": {\n          \"$ref\": \"#/definitions/pcbookMemory\"\n        }\n      }\n    },\n    \"pcbookGPU\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"brand\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"minGhz\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"maxGhz\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"memory\": {\n          \"$ref\": \"#/definitions/pcbookMemory\"\n        }\n      }\n    },\n    \"pcbookImageInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"laptopId\": {\n          \"type\": \"string\"\n        },\n        \"imageType\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"pcbookKeyboard\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"layout\": {\n          \"$ref\": \"#/definitions/KeyboardLayout\"\n        },\n        \"backlit\": {\n          \"type\": \"boolean\"\n        }\n      }\n    },\n    \"pcbookLaptop\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\"\n        },\n        \"brand\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"cpu\": {\n          \"$ref\": \"#/definitions/pcbookCPU\"\n        },\n        \"ram\": {\n          \"$ref\": \"#/definitions/pcbookMemory\"\n        },\n        \"gpus\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/pcbookGPU\"\n          }\n        },\n        \"storages\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/pcbookStorage\"\n          }\n        },\n        \"screen\": {\n          \"$ref\": \"#/definitions/pcbookScreen\"\n        },\n        \"keyboard\": {\n          \"$ref\": \"#/definitions/pcbookKeyboard\"\n        },\n        \"weightKg\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"weightLb\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"priceUsd\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"releaseYear\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"updatedAt\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        }\n      }\n    },\n    \"pcbookMemory\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"unit\": {\n          \"$ref\": \"#/definitions/MemoryUnit\"\n        }\n      }\n    },\n    \"pcbookRateLaptopRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"laptopId\": {\n          \"type\": \"string\"\n        },\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"pcbookRateLaptopResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"laptopId\": {\n          \"type\": \"string\"\n        },\n        \"ratedCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"averageScore\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"pcbookScreen\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sizeInch\": {\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        \"resolution\": {\n          \"$ref\": \"#/definitions/ScreenResolution\"\n        },\n        \"panel\": {\n          \"$ref\": \"#/definitions/ScreenPanel\"\n        },\n        \"multitouch\": {\n          \"type\": \"boolean\"\n        }\n      }\n    },\n    \"pcbookSearchLaptopResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"laptop\": {\n          \"$ref\": \"#/definitions/pcbookLaptop\"\n        }\n      }\n    },\n    \"pcbookStorage\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"driver\": {\n          \"$ref\": \"#/definitions/StorageDriver\"\n        },\n        \"memory\": {\n          \"$ref\": \"#/definitions/pcbookMemory\"\n        }\n      }\n    },\n    \"pcbookUploadImageRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"info\": {\n          \"$ref\": \"#/definitions/pcbookImageInfo\"\n        },\n        \"chunkData\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"pcbookUploadImageResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\"\n        },\n        \"size\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        }\n      }\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/memory_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"memory_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/processor_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"processor_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/screen_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"screen_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "swagger/storage_message.swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"storage_message.proto\",\n    \"version\": \"version not set\"\n  },\n  \"consumes\": [\n    \"application/json\"\n  ],\n  \"produces\": [\n    \"application/json\"\n  ],\n  \"paths\": {},\n  \"definitions\": {\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"typeUrl\": {\n          \"type\": \"string\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"format\": \"byte\"\n        }\n      }\n    },\n    \"rpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tmp/laptop.json",
    "content": "{\n  \"id\": \"22901c4c-2f34-43b6-8190-e3a57daa1a41\",\n  \"brand\": \"Apple\",\n  \"name\": \"Macbook Air\",\n  \"cpu\": {\n    \"brand\": \"AMD\",\n    \"name\": \"Ryzen 7 PRO 2700U\",\n    \"number_cores\": 2,\n    \"number_threads\": 6,\n    \"min_ghz\": 2.678712980507639,\n    \"max_ghz\": 4.812780681764419\n  },\n  \"ram\": {\n    \"value\": \"55\",\n    \"unit\": \"GIGABYTE\"\n  },\n  \"gpus\": [\n    {\n      \"brand\": \"AMD\",\n      \"name\": \"RX Vega-56\",\n      \"min_ghz\": 1.3477295700901486,\n      \"max_ghz\": 1.707348476579961,\n      \"memory\": {\n        \"value\": \"6\",\n        \"unit\": \"GIGABYTE\"\n      }\n    }\n  ],\n  \"storages\": [\n    {\n      \"driver\": \"SSD\",\n      \"memory\": {\n        \"value\": \"221\",\n        \"unit\": \"GIGABYTE\"\n      }\n    },\n    {\n      \"driver\": \"HDD\",\n      \"memory\": {\n        \"value\": \"6\",\n        \"unit\": \"TERABYTE\"\n      }\n    }\n  ],\n  \"screen\": {\n    \"size_inch\": 16.203865,\n    \"resolution\": {\n      \"width\": 2158,\n      \"height\": 1214\n    },\n    \"panel\": \"IPS\",\n    \"multitouch\": true\n  },\n  \"keyboard\": {\n    \"layout\": \"QWERTY\",\n    \"backlit\": true\n  },\n  \"weight_kg\": 1.8918947371347534,\n  \"price_usd\": 2316.488298992809,\n  \"release_year\": 2017,\n  \"updated_at\": \"2021-02-02T20:54:24.526251Z\"\n}"
  }
]