Distinguish auth/lookup failures, make votes idempotent on visible questions, bound shutdown, page admin users, LRU throttle, trusted-proxy CIDRs, avatar cleanup, versioned migrations, and session cleanup logging.
73 lines
1.7 KiB
Go
73 lines
1.7 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
// applySchema runs semicolon-separated DDL statements.
|
|
func applySchema(db *sql.DB, schema string) error {
|
|
for _, stmt := range strings.Split(schema, ";") {
|
|
stmt = strings.TrimSpace(stmt)
|
|
if stmt == "" {
|
|
continue
|
|
}
|
|
if _, err := db.Exec(stmt); err != nil {
|
|
return fmt.Errorf("%w: %s", err, stmt)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// postgresDSN normalizes DATABASE_URL for pgx (sslmode default, strip unsupported params).
|
|
func postgresDSN(raw string) (string, error) {
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return "", fmt.Errorf("DATABASE_URL: %w", err)
|
|
}
|
|
switch u.Scheme {
|
|
case "postgres", "postgresql":
|
|
default:
|
|
return "", fmt.Errorf("DATABASE_URL must be a postgres URL")
|
|
}
|
|
q := u.Query()
|
|
if strings.EqualFold(q.Get("sslrootcert"), "system") {
|
|
q.Del("sslrootcert")
|
|
}
|
|
q.Del("sslnegotiation")
|
|
if q.Get("sslmode") == "" {
|
|
q.Set("sslmode", "verify-full")
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
return u.String(), nil
|
|
}
|
|
|
|
// OpenPostgres connects to Postgres, applies schema/migrations, and starts session cleanup.
|
|
func OpenPostgres(databaseURL, schema string) (*sql.DB, *SessionStore, error) {
|
|
dsn, err := postgresDSN(databaseURL)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
db.SetMaxOpenConns(20)
|
|
db.SetMaxIdleConns(5)
|
|
if err := db.Ping(); err != nil {
|
|
_ = db.Close()
|
|
return nil, nil, fmt.Errorf("postgres ping: %w", err)
|
|
}
|
|
if err := applyMigrations(db, schema); err != nil {
|
|
_ = db.Close()
|
|
return nil, nil, fmt.Errorf("migrate: %w", err)
|
|
}
|
|
sessions := NewSessionStore(db, 5*time.Minute)
|
|
return db, sessions, nil
|
|
}
|