Go postgres simple memory sync

From UVOO Tech Wiki
Revision as of 19:24, 29 March 2025 by Busk (talk | contribs) (Created page with "``` package main import ( "context" "database/sql" "fmt" "log" "net/http" "sync" "time" _ "github.com/lib/pq"...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
package main

import (
        "context"
        "database/sql"
        "fmt"
        "log"
        "net/http"
        "sync"
        "time"

        _ "github.com/lib/pq" // PostgreSQL driver
)

type User struct {
        Username string
        Password string
}

type UserStore struct {
        mu    sync.RWMutex
        users map[string]User
}

func NewUserStore() *UserStore {
        return &UserStore{
                users: make(map[string]User),
        }
}

func (us *UserStore) Get(username string) (User, bool) {
        us.mu.RLock()
        defer us.mu.RUnlock()
        user, ok := us.users[username]
        return user, ok
}

func (us *UserStore) Set(username string, user User) {
        us.mu.Lock()
        defer us.mu.Unlock()
        us.users[username] = user
}

func (us *UserStore) Delete(username string) {
        us.mu.Lock()
        defer us.mu.Unlock()
        delete(us.users, username)
}

func syncUsers(ctx context.Context, db *sql.DB, userStore *UserStore, interval time.Duration) {
        ticker := time.NewTicker(interval)
        defer ticker.Stop()

        for {
                select {
                case <-ticker.C:
                        if err := loadUsers(db, userStore); err != nil {
                                log.Printf("Error loading users: %v", err)
                        }
                case <-ctx.Done():
                        log.Println("User sync stopped")
                        return
                }
        }
}

func loadUsers(db *sql.DB, userStore *UserStore) error {
        rows, err := db.Query("SELECT username, password FROM users")
        if err != nil {
                return fmt.Errorf("query failed: %w", err)
        }
        defer rows.Close()

        newUsers := make(map[string]User)

        for rows.Next() {
                var user User
                if err := rows.Scan(&user.Username, &user.Password); err != nil {
                        return fmt.Errorf("scan failed: %w", err)
                }
                newUsers[user.Username] = user
        }

        if err := rows.Err(); err != nil {
                return fmt.Errorf("rows iteration failed: %w", err)
        }

        userStore.mu.Lock()
        userStore.users = newUsers
        userStore.mu.Unlock()

        log.Println("Users synchronized")
        return nil
}

func basicAuth(userStore *UserStore, handler http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                username, password, ok := r.BasicAuth()
                if !ok {
                        w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
                        http.Error(w, "Unauthorized", http.StatusUnauthorized)
                        return
                }

                user, ok := userStore.Get(username)
                if !ok || user.Password != password {
                        w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
                        http.Error(w, "Unauthorized", http.StatusUnauthorized)
                        return
                }

                handler(w, r)
        }
}

func main() {
        connStr := "postgres://user:password@host/dbname?sslmode=disable" // Replace with your connection string
        db, err := sql.Open("postgres", connStr)
        if err != nil {
                log.Fatalf("Error opening database: %v", err)
        }
        defer db.Close()

        if err := db.Ping(); err != nil {
                log.Fatalf("Error pinging database: %v", err)
        }

        userStore := NewUserStore()
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()

        go syncUsers(ctx, db, userStore, 5*time.Second) // Sync every 5 seconds

        http.HandleFunc("/", basicAuth(userStore, func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprint(w, "Authenticated!\n")
        }))

        log.Println("Server listening on :8080")
        if err := http.ListenAndServe(":8080", nil); err != nil {
                log.Fatalf("Error starting server: %v", err)
        }
}