├── pkg └── entities │ ├── topic.go │ └── research.go ├── api ├── routes │ ├── topic.go │ └── research.go ├── presenter │ ├── topic.go │ └── research.go ├── app.go └── handlers │ ├── topic_handler.go │ └── research_handler.go ├── .gitignore ├── go.mod ├── README.md └── go.sum /pkg/entities/topic.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type Topic struct { 6 | ID string `json:"id"` 7 | Title string `json:"title"` 8 | CreatedAt time.Time `json:"createdAt" ` 9 | UpdatedAt time.Time `json:"updatedAt" ` 10 | } 11 | -------------------------------------------------------------------------------- /pkg/entities/research.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | // Research Constructs your research model under entities. 6 | type Research struct { 7 | ID string `json:"id"` 8 | Title string `json:"title"` 9 | Content string `json:"content"` 10 | TopicID string `json:"topic_id"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | UpdatedAt time.Time `json:"updatedAt"` 13 | } 14 | -------------------------------------------------------------------------------- /api/routes/topic.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "minerva_api/api/handlers" 5 | 6 | firebase "firebase.google.com/go" 7 | "github.com/gofiber/fiber/v2" 8 | ) 9 | 10 | // TopicRouter is the Router for GoFiber App 11 | func TopicRouter(app *fiber.App, appFire *firebase.App) { 12 | app.Get("/topics", handlers.GetTopics(appFire)) 13 | app.Post("/topics", handlers.AddTopic(appFire)) 14 | app.Put("/topics", handlers.UpdateTopic(appFire)) 15 | app.Delete("/topics", handlers.RemoveTopic(appFire)) 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | notes.txt 2 | key.json 3 | apikey.txt 4 | 5 | #My project's gitignore wasn't working before, here is why and the solution : 6 | 7 | # The files/folder in your version control will not just delete themselves just because you added them to the .gitignore. 8 | # They are already in the repository and you have to remove them. You can just do that with this: 9 | 10 | # Remember to commit everything you've changed before you do this! 11 | 12 | # git rm -rf --cached . 13 | # git add . 14 | # This removes all files from the repository and adds them back (this time respecting the rules in your .gitignore). 15 | -------------------------------------------------------------------------------- /api/routes/research.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "minerva_api/api/handlers" 5 | 6 | firebase "firebase.google.com/go" 7 | "github.com/gofiber/fiber/v2" 8 | ) 9 | 10 | // ResearchRouter is the Router for GoFiber App 11 | func ResearchRouter(app *fiber.App, appFire *firebase.App) { 12 | app.Get("/topic/researches", handlers.GetResearches(appFire)) 13 | app.Get("/topic/research/:id", handlers.GetResearchByID(appFire)) 14 | app.Post("/topic/research", handlers.AddResearch(appFire)) 15 | app.Put("/topic/research", handlers.UpdateResearch(appFire)) 16 | app.Delete("/topic/research", handlers.RemoveResearch(appFire)) 17 | app.Post("/topic/research/upload/pdf", handlers.PostPDF(appFire)) 18 | app.Post("/topic/research/upload/image", handlers.PostImage(appFire)) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /api/presenter/topic.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "minerva_api/pkg/entities" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | ) 8 | 9 | // Topic is the presenter object which will be taken in the request by Handler 10 | type Topic struct { 11 | ID string `json:"id"` 12 | Title string `json:"title"` 13 | } 14 | 15 | // TopicSuccessResponse is the singular SuccessResponse that will be passed in the response by 16 | // Handler 17 | func TopicSuccessResponse(data *entities.Topic) *fiber.Map { 18 | 19 | newTopic := Topic{ 20 | ID: data.ID, 21 | Title: data.Title, 22 | } 23 | return &fiber.Map{ 24 | "status": true, 25 | "data": newTopic, 26 | "error": nil, 27 | } 28 | } 29 | 30 | // TopicsSuccessResponse is the list SuccessResponse that will be passed in the response by Handler 31 | func TopicssSuccessResponse(data *[]Topic) *fiber.Map { 32 | return &fiber.Map{ 33 | "status": true, 34 | "data": data, 35 | "error": nil, 36 | } 37 | } 38 | 39 | // TopicErrorResponse is the ErrorResponse that will be passed in the response by Handler 40 | func TopicErrorResponse(err error) *fiber.Map { 41 | return &fiber.Map{ 42 | "status": false, 43 | "data": "", 44 | "error": err.Error(), 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/presenter/research.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "minerva_api/pkg/entities" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | ) 8 | 9 | // Research is the presenter object which will be taken in the request by Handler 10 | type Research struct { 11 | ID string `json:"id"` 12 | Title string `json:"title"` 13 | Content string `json:"content"` 14 | TopicId string `json:"topic_id"` 15 | } 16 | 17 | // ResearchSuccessResponse is the singular SuccessResponse that will be passed in the response by 18 | // Handler 19 | func ResearchSuccessResponse(data *entities.Research) *fiber.Map { 20 | 21 | newResearch := Research{ 22 | ID: data.ID, 23 | Title: data.Title, 24 | Content: data.Content, 25 | TopicId: data.TopicID, 26 | } 27 | return &fiber.Map{ 28 | "status": true, 29 | "data": newResearch, 30 | "error": nil, 31 | } 32 | } 33 | 34 | // ResearchesSuccessResponse is the list SuccessResponse that will be passed in the response by Handler 35 | func ResearchesSuccessResponse(data *[]Research) *fiber.Map { 36 | return &fiber.Map{ 37 | "status": true, 38 | "data": data, 39 | "error": nil, 40 | } 41 | } 42 | 43 | // ResearchErrorResponse is the ErrorResponse that will be passed in the response by Handler 44 | func ResearchErrorResponse(err error) *fiber.Map { 45 | return &fiber.Map{ 46 | "status": false, 47 | "data": "", 48 | "error": err.Error(), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "minerva_api/api/routes" 8 | "time" 9 | 10 | firebase "firebase.google.com/go" 11 | "github.com/gofiber/fiber/v2" 12 | "github.com/gofiber/fiber/v2/middleware/cors" 13 | "github.com/gofiber/fiber/v2/middleware/logger" 14 | recoverMw "github.com/gofiber/fiber/v2/middleware/recover" 15 | "google.golang.org/api/option" 16 | ) 17 | 18 | func main() { 19 | 20 | appFire, cancel, err := databaseConnection() 21 | if err != nil { 22 | log.Fatal("Database Connection Error $s", err) 23 | } 24 | fmt.Println("Database connection success!") 25 | 26 | //Creates new fiber app 27 | app := fiber.New() 28 | 29 | //Middlewares 30 | app.Use(logger.New()) //records the details of incoming requests when any HTTP request is made. This can be used for purposes such as debugging and performance optimization. 31 | app.Use(recoverMw.New()) //catches any errors that may cause the program to crash or interrupt and keep the server running. 32 | app.Use(cors.New()) //It helps applications bypass CORS restrictions by providing appropriate responses that allow or deny HTTP requests access to their resources. 33 | 34 | routes.ResearchRouter(app, appFire) 35 | routes.TopicRouter(app, appFire) 36 | 37 | defer cancel() 38 | defer app.Shutdown() 39 | //Starts the HTTP server 40 | log.Fatal(app.Listen(":8080")) 41 | } 42 | 43 | func databaseConnection() (*firebase.App, context.CancelFunc, error) { 44 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 45 | //Take file and return as opt for API to use 46 | opt := option.WithCredentialsFile("C:/Users/sumey/Desktop/software/Back-End/Minerva/api/key.json") 47 | //Creates a new App from the provided config and client options. 48 | appFire, err := firebase.NewApp(ctx, nil, opt) 49 | if err != nil { 50 | cancel() 51 | return nil, nil, err 52 | } 53 | 54 | return appFire, cancel, nil 55 | 56 | } 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module minerva_api 2 | 3 | go 1.19 4 | 5 | require ( 6 | firebase.google.com/go v3.13.0+incompatible 7 | github.com/gofiber/fiber/v2 v2.42.0 8 | ) 9 | 10 | require ( 11 | cloud.google.com/go/iam v0.13.0 // indirect 12 | cloud.google.com/go/storage v1.30.1 // indirect 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go v0.110.0 // indirect 17 | cloud.google.com/go/compute v1.19.0 // indirect 18 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 19 | cloud.google.com/go/firestore v1.9.0 20 | cloud.google.com/go/longrunning v0.4.1 // indirect 21 | github.com/andybalholm/brotli v1.0.4 // indirect 22 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 23 | github.com/golang/protobuf v1.5.3 // indirect 24 | github.com/google/go-cmp v0.5.9 // indirect 25 | github.com/google/uuid v1.3.0 26 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 27 | github.com/googleapis/gax-go/v2 v2.8.0 // indirect 28 | github.com/klauspost/compress v1.15.9 // indirect 29 | github.com/mattn/go-colorable v0.1.13 // indirect 30 | github.com/mattn/go-isatty v0.0.17 // indirect 31 | github.com/mattn/go-runewidth v0.0.14 // indirect 32 | github.com/philhofer/fwd v1.1.1 // indirect 33 | github.com/rivo/uniseg v0.2.0 // indirect 34 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect 35 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect 36 | github.com/tinylib/msgp v1.1.6 // indirect 37 | github.com/valyala/bytebufferpool v1.0.0 // indirect 38 | github.com/valyala/fasthttp v1.44.0 // indirect 39 | github.com/valyala/tcplisten v1.0.0 // indirect 40 | go.opencensus.io v0.24.0 // indirect 41 | golang.org/x/net v0.8.0 // indirect 42 | golang.org/x/oauth2 v0.6.0 // indirect 43 | golang.org/x/sync v0.1.0 // indirect 44 | golang.org/x/sys v0.6.0 // indirect 45 | golang.org/x/text v0.8.0 // indirect 46 | golang.org/x/time v0.3.0 // indirect 47 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 48 | google.golang.org/api v0.114.0 49 | google.golang.org/appengine v1.6.7 // indirect 50 | google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect 51 | google.golang.org/grpc v1.54.0 // indirect 52 | google.golang.org/protobuf v1.30.0 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MinervaApi 2 | **This is a simple Go and Firebase-based RESTful API designed to manage data stored in Firebase. The API allows users to create new topics and associated research. The API is designed to be easy to use and easy to set up.** 3 | 4 | ## Installation 5 | To use this API, you will need to install **Go** and the **Firebase SDK**. 6 | Create a project in the Firebase Console and add a service account. This will be used to communicate with Firebase. 7 | Add the configuration file for the Firebase SDK to the project folder. 8 | Create a Go module using the go mod init command. 9 | 10 | ## Usage 11 | To use the API, clone this repo and run the **main.go** file.(After insert your key to the project) The API listens on port 8080 by default. To create a new topic, send a POST request to /topic with a JSON payload containing the topic title and author JWT. To create a new research, send a POST request to /topic/research with a JSON payload containing the research title, content, author jwt, contributor, and topic ID. 12 | ### Necessary Variables 13 | 14 | To run this project, you will need to add your firebase key json file to project's api folder. 15 | 16 | `key.json` 17 | 18 | You can find your private key on Your firebase account -> Your firebase project -> Project Settings ->Service accounts -> Firebase Admin SDK -> (I choose Node.js) Generate new private key => 19 | your private key will be downloaded to your device. 20 | 21 | 22 | #### Example Requests 23 | 24 | ```http 25 | POST /topics HTTP/1.1 26 | Host: localhost:8080 27 | Content-Type: application/json 28 | ``` 29 | 30 | | Parameter | Type | Value | 31 | | :-------- | :------- | :------------------------- | 32 | | `title` | `string` | "Example Topic" | 33 | 34 | 35 | ```http 36 | POST /topic/research HTTP/1.1 37 | Host: localhost:8080 38 | Content-Type: application/json 39 | ``` 40 | | Parameter | Type | Value | 41 | | :-------- | :------- | :------------------------- | 42 | | `"title"` | `string` | "Example Research" | 43 | | `"content"` | `string` | "paragraph1" | 44 | | `"topic_id"` | `string` | "67890" | 45 | 46 | #### Example Responses 47 | 48 | ```http 49 | HTTP/1.1 200 OK 50 | Content-Type: application/json 51 | ``` 52 | | Parameter | Type | Value | 53 | | :-------- | :------- | :------------------------- | 54 | | `data` | `map[string]string` |` "id" `: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
`"title"` : "Research's title"
`"topic_id"` : "xxxxxxxxxxxxxxxxxxxx" | 55 | | `error` | `string` |"null" | 56 | | `status` | `string` |"true" | 57 | 58 | ```http 59 | HTTP/1.1 200 OK 60 | Content-Type: application/json 61 | ``` 62 | | Parameter | Type |Key | 63 | | :-------- | :------------------ |:-----------| 64 | | `data` | `map[string]string` |` "id" `: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
`"title"` : "Research's title"
`"topic_id"` : "xxxxxxxxxxxxxxxxxxxx"
`"content"` : "paragraph1"
| 65 | | `error` | `string` |"null" || 66 | | `status` | `string` |"true" || 67 | 68 | 69 | ## Contributing 70 | Contributions are welcome! Please feel free to fork this repository and submit pull requests. 71 | 72 | #### License 73 | -> This project is licensed by me :). 74 | -------------------------------------------------------------------------------- /api/handlers/topic_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "minerva_api/api/presenter" 8 | "minerva_api/pkg/entities" 9 | "net/http" 10 | "time" 11 | 12 | "cloud.google.com/go/firestore" 13 | firebase "firebase.google.com/go" 14 | "github.com/gofiber/fiber/v2" 15 | "google.golang.org/api/iterator" 16 | ) 17 | 18 | func GetTopics(appFire *firebase.App) fiber.Handler { 19 | Client, _ := appFire.Firestore(context.Background()) 20 | return func(c *fiber.Ctx) error { 21 | 22 | var requestBody *entities.Topic 23 | //Body Parser,Error Handler 24 | err := c.BodyParser(&requestBody) 25 | if err != nil { 26 | c.Status(http.StatusBadRequest) 27 | return c.JSON(presenter.TopicErrorResponse(err)) 28 | } 29 | 30 | var topics []presenter.Topic 31 | // Creates a reference to a collection to Topic path. 32 | userCol := Client.Collection("Topic") 33 | 34 | //Documents returns an iterator over the query's resulting documents. 35 | query := userCol.Documents(context.Background()) 36 | 37 | for { 38 | //Next returns the next result. Its second return value is iterator.Done if there are no more results 39 | doc, err := query.Next() 40 | if err == iterator.Done { 41 | break 42 | } 43 | var topic presenter.Topic 44 | doc.DataTo(&topic) 45 | topics = append(topics, topic) 46 | } 47 | 48 | return c.JSON(&fiber.Map{ 49 | "status": true, 50 | "data": &topics, 51 | "err": nil, 52 | }) 53 | } 54 | } 55 | 56 | func AddTopic(appFire *firebase.App) fiber.Handler { 57 | Client, _ := appFire.Firestore(context.Background()) 58 | return func(c *fiber.Ctx) error { 59 | 60 | var requestBody *entities.Topic 61 | 62 | //Body Parser,Error Handler 63 | if err := c.BodyParser(&requestBody); err != nil { 64 | c.Status(http.StatusBadRequest) 65 | return c.JSON(presenter.TopicErrorResponse(err)) 66 | } 67 | 68 | requestBody.CreatedAt = time.Now() 69 | requestBody.UpdatedAt = time.Now() 70 | 71 | if requestBody.ID == "" || requestBody.Title == "" { 72 | c.Status(http.StatusInternalServerError) 73 | return c.JSON(presenter.TopicErrorResponse(errors.New( 74 | "please specify title and topic id"))) 75 | } 76 | 77 | // Creates a reference to a collection to Topic path. 78 | userCol := Client.Collection("Topic") 79 | //Creates unıq id for document 80 | docRefUID := userCol.NewDoc() 81 | 82 | requestBody.ID = docRefUID.ID 83 | // Add Topic to Firestore 84 | _, err := docRefUID.Set(context.Background(), &requestBody) 85 | if err != nil { 86 | c.Status(http.StatusInternalServerError) 87 | return c.JSON(presenter.TopicErrorResponse(err)) 88 | } 89 | 90 | return c.JSON(presenter.TopicSuccessResponse(requestBody)) 91 | } 92 | } 93 | func UpdateTopic(appFire *firebase.App) fiber.Handler { 94 | Client, _ := appFire.Firestore(context.Background()) 95 | return func(c *fiber.Ctx) error { 96 | 97 | var requestBody *entities.Topic 98 | 99 | //Body Parser,Error Handler 100 | if err := c.BodyParser(&requestBody); err != nil { 101 | c.Status(http.StatusBadRequest) 102 | return c.JSON(presenter.TopicErrorResponse(err)) 103 | } 104 | 105 | if requestBody.ID == "" { 106 | c.Status(http.StatusInternalServerError) 107 | return c.JSON(presenter.TopicErrorResponse(errors.New( 108 | "please specify the Topic ID"))) 109 | } 110 | 111 | requestBody.UpdatedAt = time.Now() 112 | 113 | //Indicates the document's path 114 | docRefPath := fmt.Sprintf("Topic/%s", requestBody.ID) 115 | //Indicates to document 116 | userDoc := Client.Doc(docRefPath) 117 | 118 | //Updates the given parameters at the Document 119 | _, err := userDoc.Update(context.Background(), []firestore.Update{ 120 | {Path: "Title", Value: requestBody.Title}, 121 | {Path: "UpdatedAt", Value: requestBody.UpdatedAt}, 122 | }) 123 | 124 | if err != nil { 125 | c.Status(http.StatusInternalServerError) 126 | return c.JSON(presenter.TopicErrorResponse(err)) 127 | } 128 | 129 | return c.JSON(presenter.TopicSuccessResponse(requestBody)) 130 | } 131 | } 132 | func RemoveTopic(appFire *firebase.App) fiber.Handler { 133 | Client, _ := appFire.Firestore(context.Background()) 134 | return func(c *fiber.Ctx) error { 135 | 136 | var requestBody *entities.Topic 137 | 138 | //Body Parser,Error Handler 139 | if err := c.BodyParser(&requestBody); err != nil { 140 | c.Status(http.StatusBadRequest) 141 | return c.JSON(presenter.TopicErrorResponse(err)) 142 | } 143 | 144 | if requestBody.ID == "" { 145 | c.Status(http.StatusInternalServerError) 146 | return c.JSON(presenter.TopicErrorResponse(errors.New( 147 | "please specify the Topic ID "))) 148 | } 149 | 150 | //Indicates the document's path 151 | docRefPath := fmt.Sprintf("Topic/%s", requestBody.ID) 152 | //Indicates to document 153 | userDoc := Client.Doc(docRefPath) 154 | _, err := userDoc.Delete(context.Background()) 155 | if err != nil { 156 | c.Status(http.StatusInternalServerError) 157 | return c.JSON(presenter.TopicErrorResponse(err)) 158 | } 159 | 160 | return c.JSON(presenter.TopicSuccessResponse(requestBody)) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /api/handlers/research_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "minerva_api/api/presenter" 10 | "minerva_api/pkg/entities" 11 | "net/http" 12 | "strings" 13 | "time" 14 | 15 | "cloud.google.com/go/firestore" 16 | "cloud.google.com/go/storage" 17 | 18 | firebase "firebase.google.com/go" 19 | "github.com/gofiber/fiber/v2" 20 | "github.com/google/uuid" 21 | "google.golang.org/api/iterator" 22 | "google.golang.org/api/option" 23 | ) 24 | 25 | // AddResearch is handler/controller which creates Researches in the Database 26 | func AddResearch(appFire *firebase.App) fiber.Handler { 27 | Client, _ := appFire.Firestore(context.Background()) 28 | return func(c *fiber.Ctx) error { 29 | 30 | var requestBody *entities.Research 31 | 32 | //Body Parser,Error Handler 33 | if err := c.BodyParser(&requestBody); err != nil { 34 | c.Status(http.StatusBadRequest) 35 | return c.JSON(presenter.ResearchErrorResponse(err)) 36 | } 37 | 38 | if requestBody.TopicID == "" || requestBody.Title == "" { 39 | c.Status(http.StatusInternalServerError) 40 | return c.JSON(presenter.ResearchErrorResponse(errors.New( 41 | "please specify title and the topic id"))) 42 | } 43 | 44 | collectionName := UUID() 45 | 46 | // Creates a reference to a collection group to Research path. 47 | colPath := fmt.Sprintf("Topic/%s/%s", requestBody.TopicID, collectionName) 48 | 49 | collection := Client.Collection(colPath) 50 | 51 | // Set the ID field of the research to a new ID. 52 | requestBody.ID = collectionName 53 | 54 | // Set the created and updated timestamps for the research. 55 | requestBody.CreatedAt = time.Now() 56 | requestBody.UpdatedAt = time.Now() 57 | 58 | _, err := collection.Doc(collectionName).Set(context.Background(), &requestBody) 59 | if err != nil { 60 | c.Status(http.StatusInternalServerError) 61 | return c.JSON(presenter.ResearchErrorResponse(err)) 62 | } 63 | 64 | return c.JSON(presenter.ResearchSuccessResponse(requestBody)) 65 | } 66 | } 67 | 68 | // UpdateResearch is handler/controller which updates data of Researches in the database 69 | func UpdateResearch(appFire *firebase.App) fiber.Handler { 70 | Client, _ := appFire.Firestore(context.Background()) 71 | return func(c *fiber.Ctx) error { 72 | 73 | var requestBody *entities.Research 74 | err := c.BodyParser(&requestBody) 75 | if err != nil { 76 | c.Status(http.StatusBadRequest) 77 | return c.JSON(presenter.ResearchErrorResponse(err)) 78 | } 79 | requestBody.UpdatedAt = time.Now() 80 | //Indicates the document's path 81 | docRefPath := fmt.Sprintf("Topic/%s/%s/%s", requestBody.TopicID, requestBody.ID, requestBody.ID) 82 | //Indicates to document 83 | userDoc := Client.Doc(docRefPath) 84 | 85 | //Updates the given parameters at the Document 86 | _, err = userDoc.Update(context.Background(), []firestore.Update{ 87 | {Path: "Title", Value: requestBody.Title}, 88 | {Path: "Content", Value: requestBody.Content}, 89 | {Path: "UpdatedAt", Value: requestBody.UpdatedAt}, 90 | }) 91 | if err != nil { 92 | c.Status(http.StatusInternalServerError) 93 | return c.JSON(presenter.ResearchErrorResponse(err)) 94 | } 95 | return c.JSON(presenter.ResearchSuccessResponse(requestBody)) 96 | } 97 | } 98 | 99 | // RemoveResearch is handler/controller which removes Researches from the Database 100 | func RemoveResearch(appFire *firebase.App) fiber.Handler { 101 | Client, _ := appFire.Firestore(context.Background()) 102 | return func(c *fiber.Ctx) error { 103 | var requestBody *entities.Research 104 | err := c.BodyParser(&requestBody) 105 | if err != nil { 106 | c.Status(http.StatusBadRequest) 107 | return c.JSON(presenter.ResearchErrorResponse(err)) 108 | } 109 | 110 | //Indicates the document's path 111 | docRefPath := fmt.Sprintf("Topic/%s/%s/%s", requestBody.TopicID, requestBody.ID, requestBody.ID) 112 | 113 | //Indicates to document 114 | userDoc := Client.Doc(docRefPath) 115 | 116 | //Deletes the document 117 | _, err = userDoc.Delete(context.Background()) 118 | 119 | if err != nil { 120 | c.Status(http.StatusInternalServerError) 121 | return c.JSON(presenter.ResearchErrorResponse(err)) 122 | } 123 | return c.JSON(&fiber.Map{ 124 | "status": true, 125 | "data": "removed successfully", 126 | "err": nil, 127 | }) 128 | } 129 | } 130 | 131 | // GetResearch is handler/controller which lists single Research from the database 132 | func GetResearchByID(appFire *firebase.App) fiber.Handler { 133 | Client, _ := appFire.Firestore(context.Background()) 134 | return func(c *fiber.Ctx) error { 135 | 136 | // Extract the research ID from the URL path 137 | id := c.Params("id") 138 | 139 | var requestBody entities.Research 140 | 141 | err := c.BodyParser(&requestBody) 142 | if err != nil { 143 | c.Status(http.StatusBadRequest) 144 | return c.JSON(presenter.ResearchErrorResponse(err)) 145 | } 146 | 147 | //var researches []presenter.Research 148 | 149 | //Indicates the document's path 150 | docPath := fmt.Sprintf("Topic/%s/%s/%s", requestBody.TopicID, id, id) 151 | //Indicates to document 152 | userDoc := Client.Doc(docPath) 153 | 154 | docSnapShot, err := userDoc.Get(context.Background()) 155 | if err != nil { 156 | c.Status(http.StatusBadRequest) 157 | return c.JSON(presenter.ResearchErrorResponse(err)) 158 | } 159 | 160 | data := docSnapShot.Data() 161 | 162 | return c.JSON(&fiber.Map{ 163 | "status": true, 164 | "data": &data, 165 | "err": nil, 166 | }) 167 | } 168 | } 169 | 170 | // GetResearches is handler/controller which lists all Researches from the database 171 | func GetResearches(appFire *firebase.App) fiber.Handler { 172 | Client, _ := appFire.Firestore(context.Background()) 173 | return func(c *fiber.Ctx) error { 174 | 175 | var requestBody entities.Research 176 | err := c.BodyParser(&requestBody) 177 | if err != nil { 178 | c.Status(http.StatusBadRequest) 179 | return c.JSON(presenter.ResearchErrorResponse(err)) 180 | } 181 | 182 | var researches []presenter.Research 183 | //Indicates the document's path 184 | docPath := fmt.Sprintf("Topic/%s", requestBody.TopicID) 185 | //Indicates to document 186 | userDoc := Client.Doc(docPath) 187 | 188 | //Documents returns an iterator over the query's resulting documents. 189 | query := userDoc.Collections(context.Background()) 190 | 191 | for { 192 | //Next returns the next result. Its second return value is iterator.Done if there are no more results 193 | col, err := query.Next() 194 | if err == iterator.Done { 195 | break 196 | } 197 | if err != nil { 198 | return err 199 | } 200 | //Next returns the next result. Its second return value is iterator.Done if there are no more results 201 | doc, err := (col.Documents(context.Background())).Next() 202 | if err == iterator.Done { 203 | break 204 | } 205 | if err != nil { 206 | return err 207 | } 208 | var research presenter.Research 209 | doc.DataTo(&research) 210 | researches = append(researches, research) 211 | } 212 | 213 | return c.JSON(&fiber.Map{ 214 | "status": true, 215 | "data": &researches, 216 | "err": nil, 217 | }) 218 | 219 | } 220 | } 221 | 222 | func PostPDF(appFire *firebase.App) fiber.Handler { 223 | client, err := storage.NewClient(context.Background(), option.WithCredentialsFile("C:\\Users\\sumey\\Desktop\\software\\Back-End\\Minerva\\api\\key.json")) 224 | if err != nil { 225 | log.Printf("Failed to create client: %v", err) 226 | } 227 | 228 | firestoreClient, err := appFire.Firestore(context.Background()) 229 | if err != nil { 230 | log.Printf("Failed to create firestore client: %v", err) 231 | } 232 | return func(c *fiber.Ctx) error { 233 | // Parse the multipart form: 234 | if form, err := c.MultipartForm(); err == nil { 235 | // Get all files from "documents" key: 236 | files := form.File["pdf"] 237 | 238 | // Initialize urls slice 239 | var urls []string 240 | 241 | // Loop through files: 242 | for _, file := range files { 243 | // Save the files to Firebase Storage: 244 | wc := client.Bucket("minerva-95196.appspot.com").Object(file.Filename).NewWriter(context.Background()) 245 | wc.ContentType = file.Header["Content-Type"][0] 246 | // open the uploaded file 247 | f, err := file.Open() 248 | if err != nil { 249 | return err 250 | } 251 | // write the file to the bucket 252 | if _, err := io.Copy(wc, f); err != nil { 253 | return err 254 | } 255 | if err := wc.Close(); err != nil { 256 | return err 257 | } 258 | // print the file url 259 | url := wc.Attrs().MediaLink 260 | 261 | //Get topicID and researchID from request 262 | topicID := form.Value["topic_id"] 263 | researchID := form.Value["research_id"] 264 | 265 | //Convert []string to string 266 | topicIDstring := strings.Join(topicID, "") 267 | researchIDstring := strings.Join(researchID, "") 268 | 269 | //Indicates te firestore document path 270 | docRefPath := fmt.Sprintf("Topic/%v/%v/%v", topicIDstring, researchIDstring, researchIDstring) 271 | //Indicates to document 272 | userDoc := firestoreClient.Doc(docRefPath) 273 | //Retrieve the document 274 | docSnap, err := userDoc.Get(context.Background()) 275 | if err != nil { 276 | c.Status(http.StatusInternalServerError) 277 | return c.JSON(presenter.ResearchErrorResponse(err)) 278 | } 279 | 280 | //Get the existing URLs 281 | data := docSnap.Data() 282 | 283 | // Check if "PdfUrl" field exists in the document 284 | if pdfUrls, ok := data["PdfUrl"].([]interface{}); ok { 285 | 286 | for _, url := range pdfUrls { 287 | urls = append(urls, url.(string)) 288 | } 289 | } 290 | 291 | //Append the new URL to the existing URLs 292 | urls = append(urls, url) 293 | 294 | //Saves the pdf's url at the Document 295 | _, err = userDoc.Set(context.Background(), map[string][]string{ 296 | "PdfUrl": urls, 297 | }, firestore.MergeAll) 298 | if err != nil { 299 | c.Status(http.StatusInternalServerError) 300 | return c.JSON(presenter.ResearchErrorResponse(err)) 301 | } 302 | //Updates the given parameters at the Document 303 | _, err = userDoc.Update(context.Background(), []firestore.Update{ 304 | {Path: "UpdatedAt", Value: time.Now()}, 305 | }) 306 | if err != nil { 307 | c.Status(http.StatusInternalServerError) 308 | return c.JSON(presenter.ResearchErrorResponse(err)) 309 | } 310 | } 311 | } 312 | return c.JSON("Pdf url saved to database succesfully") 313 | } 314 | } 315 | 316 | func PostImage(appFire *firebase.App) fiber.Handler { 317 | client, err := storage.NewClient(context.Background(), option.WithCredentialsFile("C:\\Users\\sumey\\Desktop\\software\\Back-End\\Minerva\\api\\key.json")) 318 | if err != nil { 319 | log.Printf("Failed to create client: %v", err) 320 | } 321 | 322 | firestoreClient, err := appFire.Firestore(context.Background()) 323 | if err != nil { 324 | log.Printf("Failed to create firestore client: %v", err) 325 | } 326 | return func(c *fiber.Ctx) error { 327 | // Parse the multipart form: 328 | if form, err := c.MultipartForm(); err == nil { 329 | // Get all files from "documents" key: 330 | files := form.File["image"] 331 | 332 | // Initialize urls slice 333 | var urls []string 334 | 335 | // Loop through files: 336 | for _, file := range files { 337 | // Save the files to Firebase Storage: 338 | wc := client.Bucket("minerva-95196.appspot.com").Object(file.Filename).NewWriter(context.Background()) 339 | wc.ContentType = file.Header["Content-Type"][0] 340 | // open the uploaded file 341 | f, err := file.Open() 342 | if err != nil { 343 | return err 344 | } 345 | // write the file to the bucket 346 | if _, err := io.Copy(wc, f); err != nil { 347 | return err 348 | } 349 | if err := wc.Close(); err != nil { 350 | return err 351 | } 352 | // print the file url 353 | url := wc.Attrs().MediaLink 354 | 355 | //Get topicID and researchID from request 356 | topicID := form.Value["topic_id"] 357 | researchID := form.Value["research_id"] 358 | 359 | //Convert []string to string 360 | topicIDstring := strings.Join(topicID, "") 361 | researchIDstring := strings.Join(researchID, "") 362 | 363 | //Indicates te firestore document path 364 | docRefPath := fmt.Sprintf("Topic/%v/%v/%v", topicIDstring, researchIDstring, researchIDstring) 365 | //Indicates to document 366 | userDoc := firestoreClient.Doc(docRefPath) 367 | //Retrieve the document 368 | docSnap, err := userDoc.Get(context.Background()) 369 | if err != nil { 370 | c.Status(http.StatusInternalServerError) 371 | return c.JSON(presenter.ResearchErrorResponse(err)) 372 | } 373 | 374 | //Get the existing URLs 375 | data := docSnap.Data() 376 | 377 | // Check if "PdfUrl" field exists in the document 378 | if pdfUrls, ok := data["ImageUrl"].([]interface{}); ok { 379 | 380 | for _, url := range pdfUrls { 381 | urls = append(urls, url.(string)) 382 | } 383 | } 384 | 385 | //Append the new URL to the existing URLs 386 | urls = append(urls, url) 387 | 388 | //Saves the pdf's url at the Document 389 | _, err = userDoc.Set(context.Background(), map[string][]string{ 390 | "ImageUrl": urls, 391 | }, firestore.MergeAll) 392 | if err != nil { 393 | c.Status(http.StatusInternalServerError) 394 | return c.JSON(presenter.ResearchErrorResponse(err)) 395 | } 396 | //Updates the given parameters at the Document 397 | _, err = userDoc.Update(context.Background(), []firestore.Update{ 398 | {Path: "UpdatedAt", Value: time.Now()}, 399 | }) 400 | if err != nil { 401 | c.Status(http.StatusInternalServerError) 402 | return c.JSON(presenter.ResearchErrorResponse(err)) 403 | } 404 | } 405 | } 406 | return c.JSON("Image url saved to database succesfully") 407 | } 408 | } 409 | 410 | // UUID generates a uniq id 411 | func UUID() string { 412 | newUUID := uuid.New().String() 413 | return newUUID 414 | } 415 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= 3 | cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= 4 | cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= 5 | cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= 6 | cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= 7 | cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= 8 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 9 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 10 | cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= 11 | cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= 12 | cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= 13 | cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= 14 | cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= 15 | cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= 16 | cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= 17 | cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= 18 | cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= 19 | cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= 20 | cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= 21 | cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= 22 | firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= 23 | firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= 24 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 25 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 26 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 27 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 28 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 29 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 33 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 34 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 35 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 36 | github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8= 37 | github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc= 38 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 39 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 41 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 42 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 46 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 47 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 48 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 49 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 50 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 51 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 52 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 53 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 54 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 55 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 56 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 57 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 58 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 59 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 60 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 61 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 62 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 63 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 64 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 65 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 66 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 67 | github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= 68 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 69 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 70 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 71 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= 72 | github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= 73 | github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= 74 | github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= 75 | github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= 76 | github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= 77 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 78 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 79 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 80 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 81 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 82 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 83 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 84 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 85 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 86 | github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= 87 | github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 88 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 89 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 90 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 91 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 92 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= 93 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= 94 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= 95 | github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= 96 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 97 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 98 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 99 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 101 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 102 | github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= 103 | github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= 104 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 105 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 106 | github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= 107 | github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= 108 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 109 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 110 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 111 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 112 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 113 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 114 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 115 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 116 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 117 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 118 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 119 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 120 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 121 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 122 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 123 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 124 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 125 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 126 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 127 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 128 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 129 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 130 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 131 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 132 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 133 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 134 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 135 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 136 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 137 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 138 | golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= 139 | golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= 140 | golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= 141 | golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 142 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 145 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 146 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 147 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 148 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 149 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 150 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 155 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 156 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 158 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 159 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 160 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 161 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 162 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 163 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 164 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 165 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 166 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 167 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 168 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 169 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 170 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 171 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 172 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 173 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 174 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 175 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 176 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 177 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 178 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 179 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 180 | golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 181 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 183 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 184 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 185 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= 186 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 187 | google.golang.org/api v0.111.0 h1:bwKi+z2BsdwYFRKrqwutM+axAlYLz83gt5pDSXCJT+0= 188 | google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= 189 | google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= 190 | google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= 191 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 192 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 193 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 194 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 195 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 196 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 197 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 198 | google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 h1:/cadn7taPtPlCgiWNetEPsle7jgnlad2R7gR5MXB6dM= 199 | google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= 200 | google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 h1:VmCWItVXcKboEMCwZaWge+1JLiTCQSngZeINF+wzO+g= 201 | google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= 202 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 203 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 204 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 205 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 206 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 207 | google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= 208 | google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= 209 | google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= 210 | google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= 211 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 212 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 213 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 214 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 215 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 216 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 217 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 218 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 219 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 220 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 221 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 222 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 223 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 224 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 225 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 226 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 227 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 228 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 229 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 230 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 231 | --------------------------------------------------------------------------------