180 lines
6.6 KiB
Go
180 lines
6.6 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
_ "github.com/lib/pq" // PostgreSQL driver
|
|
"heckel.io/ntfy/v2/util"
|
|
"time"
|
|
)
|
|
|
|
// Messages cache
|
|
const (
|
|
pgCreateMessagesTableQuery = `
|
|
BEGIN;
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id SERIAL PRIMARY KEY,
|
|
mid TEXT NOT NULL,
|
|
time INT NOT NULL,
|
|
expires INT NOT NULL,
|
|
topic TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
priority INT NOT NULL,
|
|
tags TEXT NOT NULL,
|
|
click TEXT NOT NULL,
|
|
icon TEXT NOT NULL,
|
|
actions TEXT NOT NULL,
|
|
attachment_name TEXT NOT NULL,
|
|
attachment_type TEXT NOT NULL,
|
|
attachment_size INT NOT NULL,
|
|
attachment_expires INT NOT NULL,
|
|
attachment_url TEXT NOT NULL,
|
|
attachment_deleted BOOLEAN NOT NULL,
|
|
sender TEXT NOT NULL,
|
|
"user" TEXT NOT NULL,
|
|
content_type TEXT NOT NULL,
|
|
encoding TEXT NOT NULL,
|
|
published BOOLEAN NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
|
|
CREATE INDEX IF NOT EXISTS idx_time ON messages (time);
|
|
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
|
CREATE INDEX IF NOT EXISTS idx_expires ON messages (expires);
|
|
CREATE INDEX IF NOT EXISTS idx_sender ON messages (sender);
|
|
CREATE INDEX IF NOT EXISTS idx_user ON messages ("user");
|
|
CREATE INDEX IF NOT EXISTS idx_attachment_expires ON messages (attachment_expires);
|
|
CREATE TABLE IF NOT EXISTS stats (
|
|
key TEXT PRIMARY KEY,
|
|
value INT
|
|
);
|
|
INSERT INTO stats (key, value) VALUES ('messages', 0);
|
|
COMMIT;
|
|
`
|
|
|
|
pgSelectMessagesCountQuery = `SELECT COUNT(*) FROM messages`
|
|
)
|
|
|
|
var (
|
|
pgMessageCacheQueries = &messageCacheQueries{
|
|
insertMessage: `
|
|
INSERT INTO messages (mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, attachment_deleted, sender, "user", content_type, encoding, published)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)
|
|
`,
|
|
deleteMessage: `DELETE FROM messages WHERE mid = $1`,
|
|
updateMessagesForTopicExpiry: `UPDATE messages SET expires = $1 WHERE topic = $2`,
|
|
selectRowIDFromMessageID: `SELECT id FROM messages WHERE mid = $1`, // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
|
|
selectMessagesByID: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE mid = $1
|
|
`,
|
|
selectMessagesSinceTime: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE topic = $1 AND time >= $2 AND published = TRUE
|
|
ORDER BY time, id
|
|
`,
|
|
selectMessagesSinceTimeIncludeScheduled: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE topic = $1 AND time >= $2
|
|
ORDER BY time, id
|
|
`,
|
|
selectMessagesSinceID: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE topic = $1 AND id > $2 AND published = TRUE
|
|
ORDER BY time, id
|
|
`,
|
|
selectMessagesSinceIDIncludeScheduled: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE topic = $1 AND (id > $2 OR published = FALSE)
|
|
ORDER BY time, id
|
|
`,
|
|
selectMessagesDue: `
|
|
SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, "user", content_type, encoding
|
|
FROM messages
|
|
WHERE time <= $1 AND published = FALSE
|
|
ORDER BY time, id
|
|
`,
|
|
selectMessagesExpired: `SELECT mid FROM messages WHERE expires <= $1 AND published = TRUE`,
|
|
updateMessagePublished: `UPDATE messages SET published = TRUE WHERE mid = $1`,
|
|
selectMessageCountPerTopic: `SELECT topic, COUNT(*) FROM messages GROUP BY topic`,
|
|
selectTopics: `SELECT topic FROM messages GROUP BY topic`,
|
|
|
|
updateAttachmentDeleted: `UPDATE messages SET attachment_deleted = TRUE WHERE mid = $1`,
|
|
selectAttachmentsExpired: `SELECT mid FROM messages WHERE attachment_expires > 0 AND attachment_expires <= $1 AND attachment_deleted = FALSE`,
|
|
selectAttachmentsSizeBySender: `SELECT COALESCE(SUM(attachment_size), 0) FROM messages WHERE "user" = '' AND sender = $1 AND attachment_expires >= $2`,
|
|
selectAttachmentsSizeByUserID: `SELECT COALESCE(SUM(attachment_size), 0) FROM messages WHERE "user" = $1 AND attachment_expires >= $2`,
|
|
|
|
selectStats: `SELECT value FROM stats WHERE key = 'messages'`,
|
|
updateStats: `UPDATE stats SET value = $1 WHERE key = 'messages'`,
|
|
}
|
|
)
|
|
|
|
type pgMessageCache struct {
|
|
*commonMessageCache
|
|
}
|
|
|
|
var _ MessageCache = (*pgMessageCache)(nil)
|
|
|
|
func newPgMessageCache(connectionString, startupQueries string, batchSize int, batchTimeout time.Duration) (*pgMessageCache, error) {
|
|
db, err := sql.Open("postgres", connectionString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := setupPgMessagesDB(db, startupQueries); err != nil {
|
|
return nil, err
|
|
}
|
|
var queue *util.BatchingQueue[*message]
|
|
if batchSize > 0 || batchTimeout > 0 {
|
|
queue = util.NewBatchingQueue[*message](batchSize, batchTimeout)
|
|
}
|
|
cache := &pgMessageCache{
|
|
commonMessageCache: &commonMessageCache{
|
|
db: db,
|
|
queue: queue,
|
|
queries: pgMessageCacheQueries,
|
|
},
|
|
}
|
|
go cache.processMessageBatches()
|
|
return cache, nil
|
|
}
|
|
|
|
func setupPgMessagesDB(db *sql.DB, startupQueries string) error {
|
|
// Run startup queries
|
|
if startupQueries != "" {
|
|
if _, err := db.Exec(startupQueries); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If 'messages' table does not exist, this must be a new database
|
|
rowsMC, err := db.Query(pgSelectMessagesCountQuery)
|
|
if err != nil {
|
|
return setupNewPgCacheDB(db)
|
|
}
|
|
rowsMC.Close()
|
|
|
|
return nil
|
|
|
|
// FIXME schema migration
|
|
}
|
|
|
|
func setupNewPgCacheDB(db *sql.DB) error {
|
|
if _, err := db.Exec(pgCreateMessagesTableQuery); err != nil {
|
|
return err
|
|
}
|
|
/*
|
|
// FIXME
|
|
if _, err := db.Exec(pgCreateSchemaVersionTableQuery); err != nil {
|
|
return err
|
|
}
|
|
if _, err := db.Exec(insertSchemaVersion, currentSchemaVersion); err != nil {
|
|
return err
|
|
}
|
|
*/
|
|
return nil
|
|
}
|