diff --git a/.env b/.env index 01ab009..1d2ddd5 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ NIMS_ASSETS_DATABASE_ID= NIMS_ALERTS_DATABASE_ID= -NOTION_AUTH_TOKEN= \ No newline at end of file +NOTION_AUTH_TOKEN= +NOTION_ALERT_AGE=30 +AUTO_PURGE_ALERTS=false \ No newline at end of file diff --git a/NOTION.md b/NOTION.md index cf44b06..06d9658 100644 --- a/NOTION.md +++ b/NOTION.md @@ -13,7 +13,7 @@ You need both the `Asset Database` ID and the `Alert Database` ID to use this to * Link: `https://www.notion.so/184cdc5a1ef3710badc2d2b1271aeb81?v=174cdc3a1ef181719981000cab12bf54&pvs=4` * ID: `184cdc5a1ef3710badc2d2b1271aeb81` 4. Copy the ID -5. Repeat the above for the other database +5. Repeat the above for the `Alert Database` ## Auth Token and Access @@ -50,4 +50,4 @@ This will walk you through creating a Notion integration, getting the auth token 8. Click `Confirm` ![connection](./screenshots/confirm.png) -9. Repeat steps 7 and 8 for the `Asset Database` \ No newline at end of file +9. Repeat steps 7 and 8 for the `Incident Database` and `Asset Database` \ No newline at end of file diff --git a/README.md b/README.md index 6858202..1aa5a55 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,14 @@ Either build the binary (steps above) if you wish to make modifications, or down First, replace Notion auth token and database IDs with yours in `.env`. You can generate and configure your auth token by following the steps in [NOTION.md](./NOTION.md). + +This binary will purge alerts not associated with an incident and older than `NOTION_ALERT_AGE` (in days) automatically if `AUTO_PURGE_ALERTS` is set to `true`. ```bash NIMS_ASSETS_DATABASE_ID= NIMS_ALERTS_DATABASE_ID= NOTION_AUTH_TOKEN= +NOTION_ALERT_AGE=30 +AUTO_PURGE_ALERTS=false ``` Run the binary ```bash diff --git a/nims-webhook.go b/nims-webhook.go index 4ddca5e..f3a20b9 100644 --- a/nims-webhook.go +++ b/nims-webhook.go @@ -19,6 +19,8 @@ var ( assetsDatabaseID string alertsDatabaseID string authToken string + alertAge int + autoPurge bool ) func init() { @@ -32,11 +34,79 @@ func init() { assetsDatabaseID = os.Getenv("NIMS_ASSETS_DATABASE_ID") alertsDatabaseID = os.Getenv("NIMS_ALERTS_DATABASE_ID") authToken = os.Getenv("NOTION_AUTH_TOKEN") + autoPurgeStr := os.Getenv("AUTO_PURGE_ALERTS") + autoPurge, err = strconv.ParseBool(autoPurgeStr) + if err != nil { + log.Fatalf("Invalid boolean value for AUTO_PURGE_ALERTS: %v\n", err) + autoPurge = false + } + alertAge, err = strconv.Atoi(os.Getenv("NOTION_ALERT_AGE")) + if err != nil { + log.Fatalf("invalid value for NOTION_ALERT_AGE: %v\n", err) + } // initialize notion client client = notionapi.NewClient(notionapi.Token(authToken)) } +func deleteRecord(recordID string) error { + pageID := notionapi.PageID(recordID) + + // set archived to true + _, err := client.Page.Update(context.Background(), pageID, ¬ionapi.PageUpdateRequest{ + Archived: true, + }) + if err != nil { + return fmt.Errorf("unable to delete record: %v", err) + } + + return nil +} + +func deleteOldAlerts(databaseID string, days int) error { + + // define the filter for "Created Time" older than $days and "Related Incident" is empty + daysAgo := time.Now().AddDate(0, 0, -days) + timeObj, _ := time.Parse(time.RFC3339, daysAgo.Format(time.RFC3339)) + dateObj := notionapi.Date(timeObj) + + filter := ¬ionapi.DatabaseQueryRequest{ + Filter: notionapi.AndCompoundFilter{ + notionapi.TimestampFilter{ + Timestamp: notionapi.TimestampCreated, + CreatedTime: ¬ionapi.DateFilterCondition{ + Before: &dateObj, + }, + }, + notionapi.PropertyFilter{ + Property: "Related Incident", + Relation: ¬ionapi.RelationFilterCondition{ + IsEmpty: true, + }, + }, + }, + } + + // Query the database + response, err := client.Database.Query(context.Background(), notionapi.DatabaseID(databaseID), filter) + if err != nil { + return fmt.Errorf("failed to query the database: %v", err) + } + + // Iterate through the results and delete matching alerts + for _, result := range response.Results { + fmt.Printf("%s - deleting alert with ID %s - %s\n", time.Now().UTC().Format(time.RFC3339), result.ID, result.Properties["Name"].(*notionapi.TitleProperty).Title[0].Text.Content) + + if err := deleteRecord(string(result.ID)); err != nil { + fmt.Printf("%s - failed to delete alert with ID %s: %v\n", time.Now().UTC().Format(time.RFC3339), result.ID, err) + } else { + fmt.Printf("%s - successfully deleted alert with ID %s\n", time.Now().UTC().Format(time.RFC3339), result.ID) + } + } + + return nil +} + func checkRelatedAssetExists(name string) (notionapi.ObjectID, error) { // search for the asset by title (name) filter := notionapi.PropertyFilter{ @@ -281,8 +351,19 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) { } func main() { + + if autoPurge { + // start a goroutine for the 24-hour cron job to purge unassociated alerts + go func() { + for { + deleteOldAlerts(alertsDatabaseID, alertAge) + time.Sleep(24 * time.Hour) + } + }() + } + // listen on port 9000 for webhook POST requests http.HandleFunc("/hooks/alert", webhookHandler) - log.Println("Listening for webhooks on port 9000") + fmt.Printf("%s - listening for webhooks on port 9000\n", time.Now().UTC().Format(time.RFC3339)) log.Fatal(http.ListenAndServe(":9000", nil)) }