67a69ab073
- Go backend API with full CRUD for all modules - Rust analytics service with parallel processing - C runtime with POSIX shared memory IPC - PostgreSQL schema with 30+ tables - Redis cache, Kafka event streaming - WebSocket hub, automation engine - PDF generation, Resend email integration - JWT auth, multi-tenant - Docker Compose deployment - Nginx reverse proxy Refs: BOC-001
120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
func Connect(url string) (*sql.DB, error) {
|
|
db, err := sql.Open("postgres", url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db open: %w", err)
|
|
}
|
|
|
|
db.SetMaxOpenConns(25)
|
|
db.SetMaxIdleConns(10)
|
|
db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
if err := db.Ping(); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("db ping: %w", err)
|
|
}
|
|
|
|
if err := autoMigrate(db); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("db migrate: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func autoMigrate(db *sql.DB) error {
|
|
stmts := []string{
|
|
`CREATE TABLE IF NOT EXISTS boc_customers (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
email TEXT,
|
|
phone TEXT,
|
|
company TEXT,
|
|
status TEXT NOT NULL DEFAULT 'lead',
|
|
source TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS boc_deals (
|
|
id TEXT PRIMARY KEY,
|
|
customer_id TEXT REFERENCES boc_customers(id),
|
|
name TEXT NOT NULL,
|
|
value DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
|
status TEXT NOT NULL DEFAULT 'open',
|
|
stage TEXT NOT NULL DEFAULT 'prospect',
|
|
probability INTEGER NOT NULL DEFAULT 0,
|
|
expected_close TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS boc_invoices (
|
|
id TEXT PRIMARY KEY,
|
|
customer_id TEXT REFERENCES boc_customers(id),
|
|
amount DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
due_date TIMESTAMPTZ,
|
|
paid_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS boc_tickets (
|
|
id TEXT PRIMARY KEY,
|
|
customer_id TEXT REFERENCES boc_customers(id),
|
|
subject TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'open',
|
|
priority TEXT NOT NULL DEFAULT 'medium',
|
|
assigned_to TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ
|
|
)`,
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_customers_status ON boc_customers(status)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_deals_status ON boc_deals(status)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_invoices_status ON boc_invoices(status)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_tickets_status ON boc_tickets(status)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS boc_employees (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
email TEXT,
|
|
phone TEXT,
|
|
department TEXT,
|
|
position TEXT,
|
|
salary DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'SEK',
|
|
start_date TIMESTAMPTZ,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_employees_status ON boc_employees(status)`,
|
|
}
|
|
|
|
for _, s := range stmts {
|
|
if _, err := db.Exec(s); err != nil {
|
|
return fmt.Errorf("exec %q: %w", s[:min(40, len(s))], err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|