mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-11-13 16:10:55 +08:00
Migrate took the target version as a separate argument, kept in step with the steps by hand: adding a step without bumping the version constant silently skipped it on existing installs, while fresh installs got the new tables. The target is now the version after the last step (1 without steps), and the steps must be contiguous, so a forgotten one fails at startup. The per-store version constants are gone.
373 lines
21 KiB
Go
373 lines
21 KiB
Go
package user
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
_ "github.com/mattn/go-sqlite3" // SQLite driver
|
|
|
|
"heckel.io/ntfy/v2/db"
|
|
"heckel.io/ntfy/v2/db/schema"
|
|
"heckel.io/ntfy/v2/util"
|
|
)
|
|
|
|
const (
|
|
// User queries
|
|
sqliteSelectUsersQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
ORDER BY
|
|
CASE u.role
|
|
WHEN 'admin' THEN 1
|
|
WHEN 'anonymous' THEN 3
|
|
ELSE 2
|
|
END, u.user
|
|
`
|
|
sqliteSelectUserByIDQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
WHERE u.id = ?
|
|
`
|
|
sqliteSelectUserByNameQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
WHERE user = ?
|
|
`
|
|
sqliteSelectUserByNameOrPrimaryEmailQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
WHERE u.user = ?
|
|
OR u.id = (SELECT user_id FROM user_email WHERE email = ? AND is_primary = 1)
|
|
ORDER BY CASE WHEN u.user = ? THEN 0 ELSE 1 END
|
|
LIMIT 1
|
|
`
|
|
sqliteSelectUserByTokenQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
JOIN user_token tk on u.id = tk.user_id
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
WHERE tk.token = ? AND (tk.expires = 0 OR tk.expires >= ?)
|
|
`
|
|
sqliteSelectUserByStripeCustomerIDQuery = `
|
|
SELECT u.id, u.user, u.pass, u.role, u.prefs, u.sync_topic, u.provisioned, u.stats_messages, u.stats_emails, u.stats_calls, u.stripe_customer_id, u.stripe_subscription_id, u.stripe_subscription_status, u.stripe_subscription_interval, u.stripe_subscription_paid_until, u.stripe_subscription_cancel_at, deleted, t.id, t.code, t.name, t.messages_limit, t.messages_expiry_duration, t.emails_limit, t.calls_limit, t.reservations_limit, t.attachment_file_size_limit, t.attachment_total_size_limit, t.attachment_expiry_duration, t.attachment_bandwidth_limit, t.stripe_monthly_price_id, t.stripe_yearly_price_id
|
|
FROM user u
|
|
LEFT JOIN tier t on t.id = u.tier_id
|
|
WHERE u.stripe_customer_id = ?
|
|
`
|
|
sqliteSelectUsernamesQuery = `
|
|
SELECT user
|
|
FROM user
|
|
ORDER BY
|
|
CASE role
|
|
WHEN 'admin' THEN 1
|
|
WHEN 'anonymous' THEN 3
|
|
ELSE 2
|
|
END, user
|
|
`
|
|
sqliteSelectUserCountQuery = `SELECT COUNT(*) FROM user`
|
|
sqliteSelectUserIDFromUsernameQuery = `SELECT id FROM user WHERE user = ?`
|
|
sqliteInsertUserQuery = `INSERT INTO user (id, user, pass, role, sync_topic, provisioned, created) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
sqliteUpdateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
|
|
sqliteUpdateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
|
|
sqliteUpdateUserProvisionedQuery = `UPDATE user SET provisioned = ? WHERE user = ?`
|
|
sqliteUpdateUserPrefsQuery = `UPDATE user SET prefs = ? WHERE id = ?`
|
|
sqliteUpdateUserStatsQuery = `UPDATE user SET stats_messages = stats_messages + ?, stats_emails = stats_emails + ?, stats_calls = stats_calls + ? WHERE id = ?`
|
|
sqliteUpdateUserStatsResetAllQuery = `UPDATE user SET stats_messages = 0, stats_emails = 0, stats_calls = 0`
|
|
sqliteUpdateUserTierQuery = `UPDATE user SET tier_id = (SELECT id FROM tier WHERE code = ?) WHERE user = ?`
|
|
sqliteUpdateUserDeletedQuery = `UPDATE user SET deleted = ? WHERE id = ?`
|
|
sqliteDeleteUserQuery = `DELETE FROM user WHERE user = ?`
|
|
sqliteDeleteUserTierQuery = `UPDATE user SET tier_id = null WHERE user = ?`
|
|
sqliteDeleteUsersMarkedQuery = `DELETE FROM user WHERE deleted < ?`
|
|
sqliteDeleteUsersProvisionedQuery = `DELETE FROM user WHERE provisioned = 1`
|
|
|
|
// Access queries
|
|
sqliteSelectTopicPermsQuery = `
|
|
SELECT read, write
|
|
FROM user_access a
|
|
JOIN user u ON u.id = a.user_id
|
|
WHERE (u.user = ? OR u.user = ?) AND ? LIKE a.topic ESCAPE '\'
|
|
ORDER BY u.user DESC, LENGTH(a.topic) DESC, a.write DESC
|
|
`
|
|
sqliteSelectAccessCacheAllQuery = `
|
|
SELECT u.user, a.topic, a.read, a.write
|
|
FROM user_access a
|
|
JOIN user u ON u.id = a.user_id
|
|
`
|
|
sqliteSelectUserAllAccessQuery = `
|
|
SELECT user_id, topic, read, write, provisioned
|
|
FROM user_access
|
|
ORDER BY LENGTH(topic) DESC, write DESC, read DESC, topic
|
|
`
|
|
sqliteSelectUserAccessQuery = `
|
|
SELECT topic, read, write, provisioned
|
|
FROM user_access
|
|
WHERE user_id = (SELECT id FROM user WHERE user = ?)
|
|
ORDER BY LENGTH(topic) DESC, write DESC, read DESC, topic
|
|
`
|
|
sqliteSelectUserReservationsQuery = `
|
|
SELECT a_user.topic, a_user.read, a_user.write, a_everyone.read AS everyone_read, a_everyone.write AS everyone_write
|
|
FROM user_access a_user
|
|
LEFT JOIN user_access a_everyone ON a_user.topic = a_everyone.topic AND a_everyone.user_id = (SELECT id FROM user WHERE user = ?)
|
|
WHERE a_user.user_id = a_user.owner_user_id
|
|
AND a_user.owner_user_id = (SELECT id FROM user WHERE user = ?)
|
|
ORDER BY a_user.topic
|
|
`
|
|
sqliteSelectUserReservationsCountQuery = `
|
|
SELECT COUNT(*)
|
|
FROM user_access
|
|
WHERE user_id = owner_user_id
|
|
AND owner_user_id = (SELECT id FROM user WHERE user = ?)
|
|
`
|
|
sqliteSelectUserReservationsOwnerQuery = `
|
|
SELECT owner_user_id
|
|
FROM user_access
|
|
WHERE topic = ?
|
|
AND user_id = owner_user_id
|
|
`
|
|
sqliteSelectUserHasReservationQuery = `
|
|
SELECT COUNT(*)
|
|
FROM user_access
|
|
WHERE user_id = owner_user_id
|
|
AND owner_user_id = (SELECT id FROM user WHERE user = ?)
|
|
AND topic = ?
|
|
`
|
|
sqliteSelectOtherAccessCountQuery = `
|
|
SELECT COUNT(*)
|
|
FROM user_access
|
|
WHERE (topic = ? OR ? LIKE topic ESCAPE '\')
|
|
AND (owner_user_id IS NULL OR owner_user_id != (SELECT id FROM user WHERE user = ?))
|
|
`
|
|
sqliteUpsertUserAccessQuery = `
|
|
INSERT INTO user_access (user_id, topic, read, write, owner_user_id, provisioned)
|
|
VALUES ((SELECT id FROM user WHERE user = ?), ?, ?, ?, (SELECT IIF(?='',NULL,(SELECT id FROM user WHERE user=?))), ?)
|
|
ON CONFLICT (user_id, topic)
|
|
DO UPDATE SET read=excluded.read, write=excluded.write, owner_user_id=excluded.owner_user_id, provisioned=excluded.provisioned
|
|
`
|
|
sqliteDeleteUserAccessQuery = `
|
|
DELETE FROM user_access
|
|
WHERE user_id = (SELECT id FROM user WHERE user = ?)
|
|
OR owner_user_id = (SELECT id FROM user WHERE user = ?)
|
|
`
|
|
sqliteDeleteUserAccessProvisionedQuery = `DELETE FROM user_access WHERE provisioned = 1`
|
|
sqliteDeleteTopicAccessQuery = `
|
|
DELETE FROM user_access
|
|
WHERE (user_id = (SELECT id FROM user WHERE user = ?) OR owner_user_id = (SELECT id FROM user WHERE user = ?))
|
|
AND topic = ?
|
|
`
|
|
sqliteDeleteAllAccessQuery = `DELETE FROM user_access`
|
|
|
|
// Token queries
|
|
sqliteSelectTokenQuery = `SELECT token, label, last_access, last_origin, expires, provisioned FROM user_token WHERE user_id = ? AND token = ?`
|
|
sqliteSelectTokensQuery = `SELECT token, label, last_access, last_origin, expires, provisioned FROM user_token WHERE user_id = ?`
|
|
sqliteSelectTokenCountQuery = `SELECT COUNT(*) FROM user_token WHERE user_id = ?`
|
|
sqliteSelectAllProvisionedTokensQuery = `SELECT token, label, last_access, last_origin, expires, provisioned FROM user_token WHERE provisioned = 1`
|
|
sqliteUpsertTokenQuery = `
|
|
INSERT INTO user_token (user_id, token, label, last_access, last_origin, expires, provisioned)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT (user_id, token)
|
|
DO UPDATE SET label = excluded.label, expires = excluded.expires, provisioned = excluded.provisioned
|
|
`
|
|
sqliteUpdateTokenQuery = `UPDATE user_token SET label = ?, expires = ? WHERE user_id = ? AND token = ?`
|
|
sqliteUpdateTokenLastAccessQuery = `UPDATE user_token SET last_access = ?, last_origin = ? WHERE token = ?`
|
|
sqliteDeleteTokenQuery = `DELETE FROM user_token WHERE user_id = ? AND token = ?`
|
|
sqliteDeleteProvisionedTokenQuery = `DELETE FROM user_token WHERE token = ?`
|
|
sqliteDeleteAllProvisionedTokensQuery = `DELETE FROM user_token WHERE provisioned = 1`
|
|
sqliteDeleteAllTokenQuery = `DELETE FROM user_token WHERE user_id = ?`
|
|
sqliteDeleteExpiredTokensQuery = `DELETE FROM user_token WHERE expires > 0 AND expires < ?`
|
|
sqliteDeleteExcessTokensQuery = `
|
|
DELETE FROM user_token
|
|
WHERE user_id = ?
|
|
AND (user_id, token) NOT IN (
|
|
SELECT user_id, token
|
|
FROM user_token
|
|
WHERE user_id = ?
|
|
ORDER BY expires DESC
|
|
LIMIT ?
|
|
)
|
|
`
|
|
|
|
// Tier queries
|
|
sqliteInsertTierQuery = `
|
|
INSERT INTO tier (id, code, name, messages_limit, messages_expiry_duration, emails_limit, calls_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, attachment_bandwidth_limit, stripe_monthly_price_id, stripe_yearly_price_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`
|
|
sqliteUpdateTierQuery = `
|
|
UPDATE tier
|
|
SET name = ?, messages_limit = ?, messages_expiry_duration = ?, emails_limit = ?, calls_limit = ?, reservations_limit = ?, attachment_file_size_limit = ?, attachment_total_size_limit = ?, attachment_expiry_duration = ?, attachment_bandwidth_limit = ?, stripe_monthly_price_id = ?, stripe_yearly_price_id = ?
|
|
WHERE code = ?
|
|
`
|
|
sqliteSelectTiersQuery = `
|
|
SELECT id, code, name, messages_limit, messages_expiry_duration, emails_limit, calls_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, attachment_bandwidth_limit, stripe_monthly_price_id, stripe_yearly_price_id
|
|
FROM tier
|
|
`
|
|
sqliteSelectTierByCodeQuery = `
|
|
SELECT id, code, name, messages_limit, messages_expiry_duration, emails_limit, calls_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, attachment_bandwidth_limit, stripe_monthly_price_id, stripe_yearly_price_id
|
|
FROM tier
|
|
WHERE code = ?
|
|
`
|
|
sqliteSelectTierByPriceIDQuery = `
|
|
SELECT id, code, name, messages_limit, messages_expiry_duration, emails_limit, calls_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, attachment_bandwidth_limit, stripe_monthly_price_id, stripe_yearly_price_id
|
|
FROM tier
|
|
WHERE (stripe_monthly_price_id = ? OR stripe_yearly_price_id = ?)
|
|
`
|
|
sqliteDeleteTierQuery = `DELETE FROM tier WHERE code = ?`
|
|
|
|
// Phone queries
|
|
sqliteSelectPhoneNumbersQuery = `SELECT phone_number FROM user_phone WHERE user_id = ?`
|
|
sqliteInsertPhoneNumberQuery = `INSERT INTO user_phone (user_id, phone_number) VALUES (?, ?)`
|
|
sqliteDeletePhoneNumberQuery = `DELETE FROM user_phone WHERE user_id = ? AND phone_number = ?`
|
|
|
|
// Email queries
|
|
sqliteSelectEmailsQuery = `SELECT email, is_primary FROM user_email WHERE user_id = ? ORDER BY email`
|
|
sqliteInsertEmailQuery = `INSERT INTO user_email (user_id, email) VALUES (?, ?)`
|
|
sqliteInsertEmailIgnoreQuery = `INSERT INTO user_email (user_id, email) VALUES (?, ?) ON CONFLICT (user_id, email) DO NOTHING`
|
|
sqliteDeleteEmailQuery = `DELETE FROM user_email WHERE user_id = ? AND email = ?`
|
|
sqliteSelectPrimaryEmailQuery = `SELECT email FROM user_email WHERE user_id = ? AND is_primary = 1`
|
|
sqliteSelectUserIDByPrimaryQuery = `SELECT user_id FROM user_email WHERE email = ? AND is_primary = 1`
|
|
sqliteUpdateEmailSetPrimaryQuery = `UPDATE user_email SET is_primary = 1 WHERE user_id = ? AND email = ?`
|
|
sqliteUpdateEmailClearPrimaryQuery = `UPDATE user_email SET is_primary = 0 WHERE user_id = ? AND is_primary = 1`
|
|
|
|
// Magic link queries (email verification + password reset)
|
|
sqliteInsertMagicLinkQuery = `INSERT INTO user_magic_link (token_hash, kind, user_id, email, expires, created) VALUES (?, ?, ?, ?, ?, ?)`
|
|
sqliteSelectMagicLinkByHashQuery = `SELECT token_hash, kind, user_id, email, expires, created FROM user_magic_link WHERE token_hash = ?`
|
|
sqliteDeleteMagicLinkByHashQuery = `DELETE FROM user_magic_link WHERE token_hash = ?`
|
|
sqliteDeleteVerifyScopeQuery = `DELETE FROM user_magic_link WHERE kind = ? AND user_id = ? AND email = ?`
|
|
sqliteDeleteResetScopeQuery = `DELETE FROM user_magic_link WHERE kind = ? AND user_id = ?`
|
|
sqliteSelectPendingEmailsQuery = `SELECT email FROM user_magic_link WHERE kind = ? AND user_id = ? ORDER BY email`
|
|
sqliteDeleteExpiredMagicLinksQuery = `DELETE FROM user_magic_link WHERE expires < ?`
|
|
|
|
// Billing queries
|
|
sqliteUpdateBillingQuery = `
|
|
UPDATE user
|
|
SET stripe_customer_id = ?, stripe_subscription_id = ?, stripe_subscription_status = ?, stripe_subscription_interval = ?, stripe_subscription_paid_until = ?, stripe_subscription_cancel_at = ?
|
|
WHERE user = ?
|
|
`
|
|
)
|
|
|
|
// sqliteSelectAccessCacheUsersQuery builds the per-users cache-load query
|
|
// with a "?, ?, ..." IN clause sized for n usernames.
|
|
func sqliteSelectAccessCacheUsersQuery(n int) string {
|
|
var sb strings.Builder
|
|
sb.WriteString(`SELECT u.user, a.topic, a.read, a.write FROM user_access a JOIN user u ON u.id = a.user_id WHERE u.user IN (`)
|
|
for i := 0; i < n; i++ {
|
|
if i > 0 {
|
|
sb.WriteString(",")
|
|
}
|
|
sb.WriteString("?")
|
|
}
|
|
sb.WriteString(")")
|
|
return sb.String()
|
|
}
|
|
|
|
var sqliteQueries = queries{
|
|
selectUserByID: sqliteSelectUserByIDQuery,
|
|
selectUserByName: sqliteSelectUserByNameQuery,
|
|
selectUserByNameOrPrimaryEmail: sqliteSelectUserByNameOrPrimaryEmailQuery,
|
|
selectUserByToken: sqliteSelectUserByTokenQuery,
|
|
selectUserByStripeCustomerID: sqliteSelectUserByStripeCustomerIDQuery,
|
|
selectUsernames: sqliteSelectUsernamesQuery,
|
|
selectUsers: sqliteSelectUsersQuery,
|
|
selectUserCount: sqliteSelectUserCountQuery,
|
|
selectUserIDFromUsername: sqliteSelectUserIDFromUsernameQuery,
|
|
insertUser: sqliteInsertUserQuery,
|
|
updateUserPass: sqliteUpdateUserPassQuery,
|
|
updateUserRole: sqliteUpdateUserRoleQuery,
|
|
updateUserProvisioned: sqliteUpdateUserProvisionedQuery,
|
|
updateUserPrefs: sqliteUpdateUserPrefsQuery,
|
|
updateUserStats: sqliteUpdateUserStatsQuery,
|
|
updateUserStatsResetAll: sqliteUpdateUserStatsResetAllQuery,
|
|
updateUserTier: sqliteUpdateUserTierQuery,
|
|
updateUserDeleted: sqliteUpdateUserDeletedQuery,
|
|
deleteUser: sqliteDeleteUserQuery,
|
|
deleteUserTier: sqliteDeleteUserTierQuery,
|
|
deleteUsersMarked: sqliteDeleteUsersMarkedQuery,
|
|
deleteUsersProvisioned: sqliteDeleteUsersProvisionedQuery,
|
|
selectTopicPerms: sqliteSelectTopicPermsQuery,
|
|
selectAccessCacheAll: sqliteSelectAccessCacheAllQuery,
|
|
selectAccessCacheUsers: sqliteSelectAccessCacheUsersQuery,
|
|
selectUserAllAccess: sqliteSelectUserAllAccessQuery,
|
|
selectUserAccess: sqliteSelectUserAccessQuery,
|
|
selectUserReservations: sqliteSelectUserReservationsQuery,
|
|
selectUserReservationsCount: sqliteSelectUserReservationsCountQuery,
|
|
selectUserReservationsOwner: sqliteSelectUserReservationsOwnerQuery,
|
|
selectUserHasReservation: sqliteSelectUserHasReservationQuery,
|
|
selectOtherAccessCount: sqliteSelectOtherAccessCountQuery,
|
|
upsertUserAccess: sqliteUpsertUserAccessQuery,
|
|
deleteUserAccess: sqliteDeleteUserAccessQuery,
|
|
deleteUserAccessProvisioned: sqliteDeleteUserAccessProvisionedQuery,
|
|
deleteTopicAccess: sqliteDeleteTopicAccessQuery,
|
|
deleteAllAccess: sqliteDeleteAllAccessQuery,
|
|
selectToken: sqliteSelectTokenQuery,
|
|
selectTokens: sqliteSelectTokensQuery,
|
|
selectTokenCount: sqliteSelectTokenCountQuery,
|
|
selectAllProvisionedTokens: sqliteSelectAllProvisionedTokensQuery,
|
|
upsertToken: sqliteUpsertTokenQuery,
|
|
updateToken: sqliteUpdateTokenQuery,
|
|
updateTokenLastAccess: sqliteUpdateTokenLastAccessQuery,
|
|
deleteToken: sqliteDeleteTokenQuery,
|
|
deleteProvisionedToken: sqliteDeleteProvisionedTokenQuery,
|
|
deleteAllProvisionedTokens: sqliteDeleteAllProvisionedTokensQuery,
|
|
deleteAllToken: sqliteDeleteAllTokenQuery,
|
|
deleteExpiredTokens: sqliteDeleteExpiredTokensQuery,
|
|
deleteExcessTokens: sqliteDeleteExcessTokensQuery,
|
|
insertTier: sqliteInsertTierQuery,
|
|
selectTiers: sqliteSelectTiersQuery,
|
|
selectTierByCode: sqliteSelectTierByCodeQuery,
|
|
selectTierByPriceID: sqliteSelectTierByPriceIDQuery,
|
|
updateTier: sqliteUpdateTierQuery,
|
|
deleteTier: sqliteDeleteTierQuery,
|
|
selectPhoneNumbers: sqliteSelectPhoneNumbersQuery,
|
|
insertPhoneNumber: sqliteInsertPhoneNumberQuery,
|
|
deletePhoneNumber: sqliteDeletePhoneNumberQuery,
|
|
selectEmails: sqliteSelectEmailsQuery,
|
|
insertEmail: sqliteInsertEmailQuery,
|
|
insertEmailIgnore: sqliteInsertEmailIgnoreQuery,
|
|
deleteEmail: sqliteDeleteEmailQuery,
|
|
selectPrimaryEmail: sqliteSelectPrimaryEmailQuery,
|
|
selectUserIDByPrimary: sqliteSelectUserIDByPrimaryQuery,
|
|
updateEmailSetPrimary: sqliteUpdateEmailSetPrimaryQuery,
|
|
updateEmailClearPrimary: sqliteUpdateEmailClearPrimaryQuery,
|
|
insertMagicLink: sqliteInsertMagicLinkQuery,
|
|
selectMagicLinkByHash: sqliteSelectMagicLinkByHashQuery,
|
|
deleteMagicLinkByHash: sqliteDeleteMagicLinkByHashQuery,
|
|
deleteMagicLinkEmailVerify: sqliteDeleteVerifyScopeQuery,
|
|
deleteMagicLinkResetPassword: sqliteDeleteResetScopeQuery,
|
|
selectPendingEmails: sqliteSelectPendingEmailsQuery,
|
|
deleteExpiredMagicLinks: sqliteDeleteExpiredMagicLinksQuery,
|
|
updateBilling: sqliteUpdateBillingQuery,
|
|
}
|
|
|
|
// NewSQLiteManager creates a new Manager backed by a SQLite database
|
|
func NewSQLiteManager(filename, startupQueries string, config *Config) (*Manager, error) {
|
|
parentDir := filepath.Dir(filename)
|
|
if !util.FileExists(parentDir) {
|
|
return nil, fmt.Errorf("user database directory %s does not exist or is not accessible", parentDir)
|
|
}
|
|
// Open with case-sensitive LIKE. ACL topic matching is done via LIKE (see
|
|
// selectTopicPerms), and SQLite's LIKE is case-insensitive for ASCII by
|
|
// default -- without this, an ACL rule for "secret" would also match a
|
|
// request for "SECRET", which is a security issue. PostgreSQL's LIKE is
|
|
// already case-sensitive, so this only affects SQLite. The pragma is
|
|
// applied to every pooled connection by the driver.
|
|
d, err := sql.Open("sqlite3", fmt.Sprintf("%s?_case_sensitive_like=on", filename))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Migrations must run before the startup queries: the 5 -> 6 table rebuilds rely on
|
|
// foreign keys being OFF, which is only guaranteed on fresh connections (the foreign_keys
|
|
// pragma is enabled as part of the builtin startup queries below)
|
|
if err := schema.Migrate(d, schema.SQLite, schemaStore, sqliteCreateTables, sqliteMigrations); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := runSQLiteStartupQueries(d, startupQueries); err != nil {
|
|
return nil, err
|
|
}
|
|
return newManager(db.New(&db.Host{DB: d}, nil), sqliteQueries, config)
|
|
}
|