diff --git a/Go/db/db.go b/Go/db/db.go index edba33ba41d70faa0096c1e12adfedc2426bd6fc..c7148165296dfb1aff23ca382c6d01e976277ca3 100644 --- a/Go/db/db.go +++ b/Go/db/db.go @@ -132,7 +132,7 @@ func AddApiKey(docID, UUID string, key string) error { return fmt.Errorf(IterationFailed, err) } _ = doc - err = fmt.Errorf("API key is already registered to user") + err = errors.New("API key is already registered to user") return err } @@ -184,16 +184,16 @@ func DeleteApiKey(apiKey string) error { // If docID is empty, the API key does not exist in Firestore if docID == "" { - return fmt.Errorf("API key not found") + return errors.New("API key not found") } // Delete the document with the provided API key _, err = ref.Doc(docID).Delete(ctx) if err != nil { - return fmt.Errorf("failed to delete document: %v", err) + return fmt.Errorf("failed to delete API Key: %v", err) } - fmt.Printf("API key %s deleted successfully\n", apiKey) + log.Printf("API key %s deleted successfully\n", apiKey) return nil } @@ -297,23 +297,18 @@ func GetRegistrations(UUID string) ([]*structs.CountryInfoGet, error) { ref := client.Collection(Firestore.RegistrationCollection) // Query all documents - iter := ref.Where("UUID", "==", UUID).OrderBy("Lastchange", firestore.Desc).Documents(ctx) - defer iter.Stop() + docs, _ := ref.Where("UUID", "==", UUID).OrderBy("Lastchange", firestore.Desc).Documents(ctx).GetAll() + if err != nil { + log.Printf("Error fetching Registration: %v\n", err) + return nil, err + } var cis []*structs.CountryInfoGet - for { - doc, err := iter.Next() - if errors.Is(err, iterator.Done) { - break - } - if err != nil { - log.Printf("Error fetching document: %v", err) - return nil, err - } - ci := new(structs.CountryInfoGet) // Allocate a new instance of CountryInfoGet - if err := doc.DataTo(ci); err != nil { - log.Printf("Parsing error: %v", err) + for _, doc := range docs { + var ci *structs.CountryInfoGet + if err := doc.DataTo(&ci); err != nil { + log.Printf(ParsingError, err) return nil, err } cis = append(cis, ci) @@ -352,14 +347,12 @@ func GetSpecificRegistration(ID, UUID string) (*structs.CountryInfoGet, error) { } if err := doc.DataTo(&ci); err != nil { log.Println("Error retrieving document:", err) - // Optionally, continue to the next document instead of returning an error - // continue return nil, err } return ci, nil } - return nil, errors.New("no document with that ID was found") + return nil, errors.New("no registration with that ID was found") } func UpdateRegistration(ID, UUID string, data *structs.CountryInfoGet) error { @@ -403,7 +396,7 @@ func UpdateRegistration(ID, UUID string, data *structs.CountryInfoGet) error { return nil } - return errors.New("no document with that ID was found") + return errors.New("no registration with that ID was found") } func DeleteRegistration(ID, UUID string) error { @@ -439,7 +432,7 @@ func DeleteRegistration(ID, UUID string) error { // If docID is empty, the API key does not exist in Firestore if docID == "" { - return fmt.Errorf("ID match was not found") + return errors.New("ID match was not found") } // Delete the document with the provided API key @@ -448,12 +441,11 @@ func DeleteRegistration(ID, UUID string) error { return fmt.Errorf("failed to delete document: %v", err) } - fmt.Printf("Registration document %s deleted successfully\n", docID) + log.Printf("Registration document %s deleted successfully\n", docID) return nil } -/* -func AddWebhook(userID, docID string, webhook structs.WebhookPost) error { +func AddWebhook(docID string, webhook *structs.WebhookGet) error { client, err := getFirestoreClient() if err != nil { return err @@ -470,20 +462,14 @@ func AddWebhook(userID, docID string, webhook structs.WebhookPost) error { ref := client.Collection(Firestore.WebhookCollection) // Create a new document and add it to the - _, err = ref.Doc(docID).Set(ctx, map[string]interface{}{ - "id": userID, - "url": webhook.URL, - "country": webhook.Country, - "event": webhook.Event, - "lastChange": firestore.ServerTimestamp - }) + _, err = ref.Doc(docID).Set(ctx, webhook) if err != nil { log.Printf(FirebaseClosingErr, err) return err } return nil -}*/ +} func GetAllWebhooks() ([]structs.WebhookGet, error) { client, err := getFirestoreClient() @@ -502,7 +488,7 @@ func GetAllWebhooks() ([]structs.WebhookGet, error) { // Query all documents docs, err := ref.Documents(ctx).GetAll() if err != nil { - log.Printf("Error fetching documents: %v\n", err) + log.Printf("Error fetching all stored Webhooks: %v\n", err) return nil, err } @@ -512,8 +498,6 @@ func GetAllWebhooks() ([]structs.WebhookGet, error) { var webhook structs.WebhookGet if err := doc.DataTo(&webhook); err != nil { log.Printf(ParsingError, err) - // Optionally, continue to the next document instead of returning an error - // continue return nil, err } webhooks = append(webhooks, webhook) @@ -522,7 +506,7 @@ func GetAllWebhooks() ([]structs.WebhookGet, error) { return webhooks, nil } -func GetWebhooksUser(UUID string) ([]structs.WebhookGet, error) { +func GetWebhooksUser(UUID string) ([]structs.WebhookResponse, error) { client, err := getFirestoreClient() if err != nil { return nil, err @@ -536,10 +520,45 @@ func GetWebhooksUser(UUID string) ([]structs.WebhookGet, error) { // Reference to the Firestore collection ref := client.Collection(Firestore.WebhookCollection) - iter := ref.Where("UUID", "==", UUID).Limit(1).Documents(ctx) + // Query all documents + docs, err := ref.Where("UUID", "==", UUID).Documents(ctx).GetAll() + if err != nil { + log.Printf("Error fetching users webhooks: %v\n", err) + return nil, err + } + + var webhooks []structs.WebhookResponse + + for _, doc := range docs { + var webhook structs.WebhookResponse + if err := doc.DataTo(&webhook); err != nil { + log.Printf(ParsingError, err) + return nil, err + } + webhooks = append(webhooks, webhook) + } + + return webhooks, nil +} + +func GetSpecificWebhook(ID, UUID string) (*structs.WebhookResponse, error) { + client, err := getFirestoreClient() + if err != nil { + return nil, err + } + defer func() { + if err := client.Close(); err != nil { + log.Printf(FirebaseClosingErr, err) + } + }() + + // Reference to the Firestore collection + ref := client.Collection(Firestore.WebhookCollection) + + iter := ref.Where("ID", "==", ID).Where("UUID", "==", UUID).Limit(1).Documents(ctx) defer iter.Stop() - var webhooks []structs.WebhookGet + var webhook *structs.WebhookResponse // Iterate over the query results for { @@ -550,13 +569,58 @@ func GetWebhooksUser(UUID string) ([]structs.WebhookGet, error) { if err != nil { return nil, fmt.Errorf(IterationFailed, err) } - var webhook structs.WebhookGet if err := doc.DataTo(&webhook); err != nil { - log.Printf(ParsingError, err) + log.Println("Error retrieving document:", err) return nil, err } - webhooks = append(webhooks, webhook) + return webhook, nil } - return webhooks, nil + return nil, errors.New("no document with that ID was found") +} + +func DeleteWebhook(ID, UUID string) error { + client, err := getFirestoreClient() + if err != nil { + return err + } + defer func(client *firestore.Client) { + err := client.Close() + if err != nil { + log.Printf(FirebaseClosingErr, err) + return + } + }(client) + + // Create a reference to the Firestore collection + ref := client.Collection(Firestore.WebhookCollection) + + iter := ref.Where("ID", "==", ID).Where("UUID", "==", UUID).Limit(1).Documents(ctx) + defer iter.Stop() + + var docID string + for { + doc, err := iter.Next() + if errors.Is(err, iterator.Done) { + break + } + if err != nil { + return fmt.Errorf(IterationFailed, err) + } + docID = doc.Ref.ID + } + + // If docID is empty, the API key does not exist in Firestore + if docID == "" { + return fmt.Errorf("ID match was not found") + } + + // Delete the document with the provided API key + _, err = ref.Doc(docID).Delete(ctx) + if err != nil { + return fmt.Errorf("failed to delete document: %v", err) + } + + log.Printf("Webhook %s deleted successfully\n\n", docID) + return nil } diff --git a/Go/internal/handlers/endpoint/dashboard/dashboards_handler.go b/Go/internal/handlers/endpoint/dashboard/dashboards_handler.go index 69ae84fbe5e15f1daed5976330c61e287b5d481a..82178e1285e22db1bb51a118bd7b88eb9c9bd04c 100644 --- a/Go/internal/handlers/endpoint/dashboard/dashboards_handler.go +++ b/Go/internal/handlers/endpoint/dashboard/dashboards_handler.go @@ -23,7 +23,7 @@ func DashboardsHandler(w http.ResponseWriter, r *http.Request) { case http.MethodGet: handleDashboardGetRequest(w, r) default: - http.Error(w, "REST Method: "+r.Method+" not supported. Currently only"+http.MethodGet+" methods are supported.", http.StatusNotImplemented) + http.Error(w, "REST Method: "+r.Method+" not supported. Only supported methods for this endpoint is:\n"+http.MethodGet, http.StatusNotImplemented) return } } diff --git a/Go/internal/handlers/endpoint/dashboard/notifications_handler.go b/Go/internal/handlers/endpoint/dashboard/notifications_handler.go index 468408afa3de13a2c5f73dd45141755e84b3e4d3..f4efe272ac4d661ba36e53f96fa49b9a2b4270f1 100644 --- a/Go/internal/handlers/endpoint/dashboard/notifications_handler.go +++ b/Go/internal/handlers/endpoint/dashboard/notifications_handler.go @@ -2,21 +2,126 @@ package dashboard import ( + "encoding/json" + "fmt" + "globeboard/db" + _func "globeboard/internal/func" + "globeboard/internal/utils/constants" + "globeboard/internal/utils/structs" + "log" "net/http" ) // NotificationsHandler handles requests to retrieve readership dashboard for a specific language. func NotificationsHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { + case http.MethodPost: + handleNotifPostRequest(w, r) case http.MethodGet: - handleNotifGetRequest(w, r) + handleNotifGetAllRequest(w, r) default: - http.Error(w, "REST Method: "+r.Method+" not supported. Currently no methods are supported.", http.StatusNotImplemented) + http.Error(w, "REST Method: "+r.Method+" not supported. Only supported methods for this endpoint is:\n"+http.MethodPost+"\n"+http.MethodGet+"\n"+http.MethodPatch, http.StatusNotImplemented) return } } -// handleGetRequest handles GET requests to retrieve readership dashboard for a specific language. -func handleNotifGetRequest(w http.ResponseWriter, r *http.Request) { - //TODO::Complete HTTP Method Requests +func handleNotifPostRequest(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + token := query.Get("token") + if token == "" { + http.Error(w, ProvideAPI, http.StatusUnauthorized) + return + } + UUID := db.GetAPIKeyUUID(token) + if UUID == "" { + err := fmt.Sprintf(APINotAccepted) + http.Error(w, err, http.StatusNotAcceptable) + return + } + + if r.Body == nil { + err := fmt.Sprintf("Please send a request body") + http.Error(w, err, http.StatusBadRequest) + return + } + + var webhook *structs.WebhookGet + if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil { + err := fmt.Sprintf("Error decoding request body: %v", err) + http.Error(w, err, http.StatusBadRequest) + return + } + + UDID := _func.GenerateUID(constants.DocIdLength) + ID := _func.GenerateUID(constants.IdLength) + + webhook.ID = ID + webhook.UUID = UUID + + err := db.AddWebhook(UDID, webhook) + if err != nil { + log.Println("Error saving data to database" + err.Error()) + http.Error(w, "Error storing data in database", http.StatusInternalServerError) + return + } + + hook, err := db.GetSpecificWebhook(ID, UUID) + if err != nil { + log.Print("Error getting document from database: ", err) + http.Error(w, "Error confirming data added to database", http.StatusInternalServerError) + return + } + + response := map[string]interface{}{ + "id": hook.ID, + } + + // Set Content-Type header + w.Header().Set(ContentType, ApplicationJSON) + + // Write the status code to the response + w.WriteHeader(http.StatusCreated) + + // Serialize the struct to JSON and write it to the response + err = json.NewEncoder(w).Encode(response) + if err != nil { + // Handle error + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func handleNotifGetAllRequest(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + token := query.Get("token") + if token == "" { + http.Error(w, ProvideAPI, http.StatusUnauthorized) + return + } + UUID := db.GetAPIKeyUUID(token) + if UUID == "" { + err := fmt.Sprintf(APINotAccepted) + http.Error(w, err, http.StatusNotAcceptable) + return + } + regs, err := db.GetWebhooksUser(UUID) + if err != nil { + errmsg := fmt.Sprint("Error retrieving document from database: ", err) + http.Error(w, errmsg, http.StatusInternalServerError) + return + } + + // Set Content-Type header + w.Header().Set(ContentType, ApplicationJSON) + + // Write the status code to the response + w.WriteHeader(http.StatusOK) + + // Serialize the struct to JSON and write it to the response + err = json.NewEncoder(w).Encode(regs) + if err != nil { + // Handle error + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } diff --git a/Go/internal/handlers/endpoint/dashboard/notifications_id_handler.go b/Go/internal/handlers/endpoint/dashboard/notifications_id_handler.go index f059363c096bd6106d9b9d00679ec0be6942b4da..def3461d7ee75f3c86fdb463aea3549636bbd849 100644 --- a/Go/internal/handlers/endpoint/dashboard/notifications_id_handler.go +++ b/Go/internal/handlers/endpoint/dashboard/notifications_id_handler.go @@ -2,6 +2,10 @@ package dashboard import ( + "encoding/json" + "fmt" + "globeboard/db" + "log" "net/http" ) @@ -9,14 +13,81 @@ import ( func NotificationsIdHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - handleNotifGetAllRequest(w, r) + handleNotifGetRequest(w, r) + case http.MethodDelete: + handleNotifDeleteRequest(w, r) default: - http.Error(w, "REST Method: "+r.Method+" not supported. Currently no methods are supported.", http.StatusNotImplemented) + http.Error(w, "REST Method: "+r.Method+" not supported. Only supported methods for this endpoint is:\n"+http.MethodPost+"\n"+http.MethodGet+"\n"+http.MethodPatch, http.StatusNotImplemented) return } } -// handleGetRequest handles GET requests to retrieve readership dashboard for a specific language. -func handleNotifGetAllRequest(w http.ResponseWriter, r *http.Request) { - //TODO::Complete HTTP Method Requests +func handleNotifGetRequest(w http.ResponseWriter, r *http.Request) { + ID := r.PathValue("ID") + query := r.URL.Query() + token := query.Get("token") + if token == "" { + http.Error(w, ProvideAPI, http.StatusUnauthorized) + return + } + UUID := db.GetAPIKeyUUID(token) + if UUID == "" { + err := fmt.Sprintf(APINotAccepted) + http.Error(w, err, http.StatusNotAcceptable) + return + } + if ID == "" { + http.Error(w, ProvideID, http.StatusBadRequest) + return + } + + hook, err := db.GetSpecificWebhook(ID, UUID) + if err != nil { + log.Print("Error getting document from database: ", err) + http.Error(w, "Error retrieving data from database", http.StatusInternalServerError) + return + } + + // Set Content-Type header + w.Header().Set(ContentType, ApplicationJSON) + + // Write the status code to the response + w.WriteHeader(http.StatusOK) + + // Serialize the struct to JSON and write it to the response + err = json.NewEncoder(w).Encode(hook) + if err != nil { + // Handle error + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func handleNotifDeleteRequest(w http.ResponseWriter, r *http.Request) { + ID := r.PathValue("ID") + query := r.URL.Query() + token := query.Get("token") + if token == "" { + http.Error(w, ProvideAPI, http.StatusUnauthorized) + return + } + UUID := db.GetAPIKeyUUID(token) + if UUID == "" { + err := fmt.Sprintf(APINotAccepted) + http.Error(w, err, http.StatusNotAcceptable) + return + } + if ID == "" { + http.Error(w, ProvideID, http.StatusBadRequest) + return + } + + err := db.DeleteWebhook(ID, UUID) + if err != nil { + err := fmt.Sprintf("Error deleting data from database: %v", err) + http.Error(w, err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) } diff --git a/Go/internal/handlers/endpoint/dashboard/registrations_handler.go b/Go/internal/handlers/endpoint/dashboard/registrations_handler.go index f4b8b23a30ca1199a0ca4941184e97535365e87b..3835b43d608c6e2d8b27febb30fba7ed20b9bf49 100644 --- a/Go/internal/handlers/endpoint/dashboard/registrations_handler.go +++ b/Go/internal/handlers/endpoint/dashboard/registrations_handler.go @@ -79,7 +79,7 @@ func handleRegPostRequest(w http.ResponseWriter, r *http.Request) { } UDID := _func.GenerateUID(constants.DocIdLength) - URID := _func.GenerateUID(constants.RegIdLength) + URID := _func.GenerateUID(constants.IdLength) ci.ID = URID ci.UUID = UUID diff --git a/Go/internal/utils/constants/constants.go b/Go/internal/utils/constants/constants.go index 996fdc63c8e20d7ad739ab5d5672413c0b7ba64e..91f5a8164f04a61163b27a0d3ee8533d326d1b3b 100644 --- a/Go/internal/utils/constants/constants.go +++ b/Go/internal/utils/constants/constants.go @@ -7,5 +7,5 @@ const ( FirebaseCredentialPath = "./.secrets/firebaseCredentials.json" ApiKeyLength = 20 DocIdLength = 24 - RegIdLength = 16 + IdLength = 16 ) diff --git a/Go/internal/utils/structs/structs.go b/Go/internal/utils/structs/structs.go index 9e43e3c178d0287800b421df17d7f9cb60582b32..8e3840459dd4516879277c09ea8f789a39f96ea6 100644 --- a/Go/internal/utils/structs/structs.go +++ b/Go/internal/utils/structs/structs.go @@ -69,9 +69,10 @@ type StatusResponse struct { // Webhooks Structs -type WebhookPost struct { +type WebhookResponse struct { + ID string `json:"id"` URL string `json:"url"` - Country string `json:"country"` + Country string `json:"country,omitempty"` Event []string `json:"event"` }