Compare commits

..

No commits in common. "main" and "v0.2.1" have entirely different histories.
main ... v0.2.1

3 changed files with 5 additions and 43 deletions

29
api.go
View file

@ -2,14 +2,12 @@ package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/slack-go/slack"
"gorm.io/gorm"
)
// Commonly used strings.
@ -100,34 +98,13 @@ func (s *HTTPServer) RegisterAPIRoutes(r *mux.Router) {
now := time.Now().UTC()
conversation := app.config.Slack.DefaultConversation
// Find plan times that are occuring right now. A 60-minute buffer
// is applied past ends_at so services that run long still resolve
// to their event channel instead of falling back to the default.
// Order by starts_at DESC so the most recent active service wins
// when a later service's window overlaps an earlier service's buffer.
// Find plan times that are occuring right now.
var planTime PlanTimes
err = app.db.Where("time_type='service' AND starts_at < ? AND ends_at > ?", now, now.Add(-60*time.Minute)).Order("starts_at DESC").First(&planTime).Error
// A "record not found" simply means no service is occuring right now, in
// which case we fall back to the default conversation. Any other error
// (e.g. "database is locked") must NOT be swallowed: treating it as "no
// service" would silently misroute the message to the default
// conversation instead of the event channel. Fail so the caller retries.
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.Println("Error looking up plan time:", err)
s.APISendGeneralResp(w, APIERR, "Error looking up plan time")
return
}
app.db.Where("time_type='service' AND starts_at < ? AND ends_at > ?", now, now).First(&planTime)
if planTime.Plan != 0 {
// If plan found, check for the slack channel.
var channel SlackChannels
err = app.db.Where("pc_plan = ?", planTime.Plan).First(&channel).Error
// As above, only "record not found" is a benign result here. On any
// other error we must not fall through to the default conversation.
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.Println("Error looking up slack channel:", err)
s.APISendGeneralResp(w, APIERR, "Error looking up slack channel")
return
}
app.db.Where("pc_plan = ?", planTime.Plan).First(&channel)
if channel.ID != "" {
// If slack channel found, update the conversation to the channel ID.
conversation = channel.ID

View file

@ -2,7 +2,6 @@ package main
import (
"log"
"strings"
"time"
"gorm.io/driver/mysql"
@ -127,21 +126,7 @@ func (a *App) InitDB() {
}
// Depending on connection configuration, open the database.
if a.config.DB.Type == "sqlite3" {
// Enable WAL journaling and a busy timeout. Without WAL, a single
// long-running writer (e.g. the channel-creation/sync routine, which
// interleaves slow Slack API calls with its writes) blocks all readers,
// causing "database is locked" on concurrent reads such as the
// send_message channel lookup. WAL lets reads proceed alongside the
// writer, and the busy timeout makes any remaining contention wait
// rather than fail immediately. Append as DSN pragmas, preserving any
// query string already present in the configured connection.
conn := a.config.DB.Connection
sep := "?"
if strings.Contains(conn, "?") {
sep = "&"
}
conn += sep + "_journal_mode=WAL&_busy_timeout=10000"
a.db, err = gorm.Open(sqlite.Open(conn), dbConfig)
a.db, err = gorm.Open(sqlite.Open(a.config.DB.Connection), dbConfig)
} else if a.config.DB.Type == "mysql" {
a.db, err = gorm.Open(mysql.Open(a.config.DB.Connection), dbConfig)
} else if a.config.DB.Type == "postgres" {

View file

@ -13,7 +13,7 @@ import (
const (
serviceName = "service-notifications"
serviceDescription = "Notifications for church services"
serviceVersion = "0.2.2"
serviceVersion = "0.2.1"
)
// App is the global application structure for communicating between servers and storing information.