chore(go): create webhooks repository. Closes #4085 (#4146)

This commit is contained in:
Gabe Kangas
2025-01-18 15:40:10 -08:00
committed by GitHub
parent da9d5b8411
commit 6abbf8f50c
7 changed files with 108 additions and 49 deletions

View File

@@ -0,0 +1,28 @@
package tables
import (
"database/sql"
log "github.com/sirupsen/logrus"
)
func CreateWebhooksTable(db *sql.DB) {
log.Traceln("Creating webhooks table...")
createTableSQL := `CREATE TABLE IF NOT EXISTS webhooks (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"url" string NOT NULL,
"events" TEXT NOT NULL,
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
"last_used" DATETIME
);`
stmt, err := db.Prepare(createTableSQL)
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
if _, err = stmt.Exec(); err != nil {
log.Warnln(err)
}
}

View File

@@ -0,0 +1,229 @@
package webhookrepository
import (
"errors"
"fmt"
"strings"
"time"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
log "github.com/sirupsen/logrus"
)
type WebhookRepository interface {
InsertWebhook(url string, events []models.EventType) (int, error)
DeleteWebhook(id int) error
GetWebhooksForEvent(event models.EventType) []models.Webhook
GetWebhooks() ([]models.Webhook, error)
SetWebhookAsUsed(webhook models.Webhook) error
}
type SqlWebhookRepository struct {
datastore *data.Datastore
}
// NOTE: This is temporary during the transition period.
var temporaryGlobalInstance WebhookRepository
// Get will return the user repository.
func Get() WebhookRepository {
if temporaryGlobalInstance == nil {
i := New(data.GetDatastore())
temporaryGlobalInstance = i
}
return temporaryGlobalInstance
}
// New will create a new instance of the UserRepository.
func New(datastore *data.Datastore) WebhookRepository {
r := SqlWebhookRepository{
datastore: datastore,
}
return &r
}
// InsertWebhook will add a new webhook to the database.
func (r *SqlWebhookRepository) InsertWebhook(url string, events []models.EventType) (int, error) {
log.Traceln("Adding new webhook")
eventsString := strings.Join(events, ",")
tx, err := r.datastore.DB.Begin()
if err != nil {
return 0, err
}
stmt, err := tx.Prepare("INSERT INTO webhooks(url, events) values(?, ?)")
if err != nil {
return 0, err
}
defer stmt.Close()
insertResult, err := stmt.Exec(url, eventsString)
if err != nil {
return 0, err
}
if err = tx.Commit(); err != nil {
return 0, err
}
newID, err := insertResult.LastInsertId()
if err != nil {
return 0, err
}
return int(newID), err
}
// DeleteWebhook will delete a webhook from the database.
func (r *SqlWebhookRepository) DeleteWebhook(id int) error {
log.Traceln("Deleting webhook")
tx, err := r.datastore.DB.Begin()
if err != nil {
return err
}
stmt, err := tx.Prepare("DELETE FROM webhooks WHERE id = ?")
if err != nil {
return err
}
defer stmt.Close()
result, err := stmt.Exec(id)
if err != nil {
return err
}
if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 0 {
_ = tx.Rollback()
return errors.New(fmt.Sprint(id) + " not found")
}
if err = tx.Commit(); err != nil {
return err
}
return nil
}
// GetWebhooksForEvent will return all of the webhooks that want to be notified about an event type.
func (r *SqlWebhookRepository) GetWebhooksForEvent(event models.EventType) []models.Webhook {
webhooks := make([]models.Webhook, 0)
query := `SELECT * FROM (
WITH RECURSIVE split(id, url, event, rest) AS (
SELECT id, url, '', events || ',' FROM webhooks
UNION ALL
SELECT id, url,
substr(rest, 0, instr(rest, ',')),
substr(rest, instr(rest, ',')+1)
FROM split
WHERE rest <> '')
SELECT id, url, event
FROM split
WHERE event <> ''
) AS webhook WHERE event IS ?`
rows, err := r.datastore.DB.Query(query, event)
if err != nil || rows.Err() != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var url string
if err := rows.Scan(&id, &url, &event); err != nil {
log.Debugln(err)
log.Error("There is a problem with the database.")
break
}
singleWebhook := models.Webhook{
ID: id,
URL: url,
}
webhooks = append(webhooks, singleWebhook)
}
return webhooks
}
// GetWebhooks will return all the webhooks.
func (r *SqlWebhookRepository) GetWebhooks() ([]models.Webhook, error) { //nolint
webhooks := make([]models.Webhook, 0)
query := "SELECT * FROM webhooks"
rows, err := r.datastore.DB.Query(query)
if err != nil {
return webhooks, err
}
defer rows.Close()
for rows.Next() {
var id int
var url string
var events string
var timestampString string
var lastUsedString *string
if err := rows.Scan(&id, &url, &events, &timestampString, &lastUsedString); err != nil {
log.Error("There is a problem reading the database.", err)
return webhooks, err
}
timestamp, err := time.Parse(time.RFC3339, timestampString)
if err != nil {
return webhooks, err
}
var lastUsed *time.Time
if lastUsedString != nil {
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
lastUsed = &lastUsedTime
}
singleWebhook := models.Webhook{
ID: id,
URL: url,
Events: strings.Split(events, ","),
Timestamp: timestamp,
LastUsed: lastUsed,
}
webhooks = append(webhooks, singleWebhook)
}
if err := rows.Err(); err != nil {
return webhooks, err
}
return webhooks, nil
}
// SetWebhookAsUsed will update the last used time for a webhook.
func (r *SqlWebhookRepository) SetWebhookAsUsed(webhook models.Webhook) error {
tx, err := r.datastore.DB.Begin()
if err != nil {
return err
}
stmt, err := tx.Prepare("UPDATE webhooks SET last_used = CURRENT_TIMESTAMP WHERE id = ?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := stmt.Exec(webhook.ID); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return nil
}