Initial vibeproxy implementation

Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
2026-08-17 10:28:59 -05:00
co-authored by Claude Sonnet 5
commit ba1f7c4c03
105 changed files with 9142 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
package database
import (
"database/sql"
"errors"
"github.com/owen/vibeproxy/internal/models"
)
func ListAccessLists(db *sql.DB) ([]models.AccessList, error) {
rows, err := db.Query(`SELECT id, name, default_allow, comments, created_at, updated_at FROM access_lists ORDER BY id`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.AccessList
for rows.Next() {
a, err := scanAccessList(rows)
if err != nil {
return nil, err
}
out = append(out, a)
}
if err := rows.Err(); err != nil {
return nil, err
}
for i := range out {
if err := hydrateAccessList(db, &out[i]); err != nil {
return nil, err
}
}
return out, nil
}
func scanAccessList(rs rowScanner) (models.AccessList, error) {
var a models.AccessList
var defaultAllow int
var created, updated string
err := rs.Scan(&a.ID, &a.Name, &defaultAllow, &a.Comments, &created, &updated)
if err != nil {
return a, err
}
a.DefaultAllow = defaultAllow == 1
a.CreatedAt = parseTime(created)
a.UpdatedAt = parseTime(updated)
return a, nil
}
func hydrateAccessList(db *sql.DB, a *models.AccessList) error {
rrows, err := db.Query(`SELECT id, access_list_id, action, cidr, priority FROM access_rules WHERE access_list_id = ? ORDER BY priority, id`, a.ID)
if err != nil {
return err
}
for rrows.Next() {
var r models.AccessRule
if err := rrows.Scan(&r.ID, &r.AccessListID, &r.Action, &r.CIDR, &r.Priority); err != nil {
rrows.Close()
return err
}
a.Rules = append(a.Rules, r)
}
rrows.Close()
if err := rrows.Err(); err != nil {
return err
}
urows, err := db.Query(`SELECT id, access_list_id, username, password_hash FROM access_list_users WHERE access_list_id = ? ORDER BY id`, a.ID)
if err != nil {
return err
}
for urows.Next() {
var u models.AccessListUser
if err := urows.Scan(&u.ID, &u.AccessListID, &u.Username, &u.PasswordHash); err != nil {
urows.Close()
return err
}
a.BasicAuthUsers = append(a.BasicAuthUsers, u)
}
urows.Close()
return urows.Err()
}
func GetAccessList(db *sql.DB, id int64) (*models.AccessList, error) {
row := db.QueryRow(`SELECT id, name, default_allow, comments, created_at, updated_at FROM access_lists WHERE id = ?`, id)
a, err := scanAccessList(row)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
if err := hydrateAccessList(db, &a); err != nil {
return nil, err
}
return &a, nil
}
func SaveAccessList(db *sql.DB, a *models.AccessList) (int64, error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
if a.ID == 0 {
res, err := tx.Exec(`INSERT INTO access_lists (name, default_allow, comments) VALUES (?, ?, ?)`, a.Name, boolToInt(a.DefaultAllow), a.Comments)
if err != nil {
return 0, err
}
a.ID, err = res.LastInsertId()
if err != nil {
return 0, err
}
} else {
if _, err := tx.Exec(`UPDATE access_lists SET name=?, default_allow=?, comments=?, updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id=?`,
a.Name, boolToInt(a.DefaultAllow), a.Comments, a.ID); err != nil {
return 0, err
}
if _, err := tx.Exec(`DELETE FROM access_rules WHERE access_list_id = ?`, a.ID); err != nil {
return 0, err
}
if _, err := tx.Exec(`DELETE FROM access_list_users WHERE access_list_id = ?`, a.ID); err != nil {
return 0, err
}
}
for _, r := range a.Rules {
if _, err := tx.Exec(`INSERT INTO access_rules (access_list_id, action, cidr, priority) VALUES (?,?,?,?)`,
a.ID, r.Action, r.CIDR, r.Priority); err != nil {
return 0, err
}
}
for _, u := range a.BasicAuthUsers {
if _, err := tx.Exec(`INSERT INTO access_list_users (access_list_id, username, password_hash) VALUES (?,?,?)`,
a.ID, u.Username, u.PasswordHash); err != nil {
return 0, err
}
}
if err := tx.Commit(); err != nil {
return 0, err
}
return a.ID, nil
}
func DeleteAccessList(db *sql.DB, id int64) error {
_, err := db.Exec(`DELETE FROM access_lists WHERE id = ?`, id)
return err
}