Database Architecture
Overview
cpm uses SQLite3 as its embedded database for storing repository metadata, user information, organization structures, SSH keys, and server configurations. This document provides comprehensive coverage of the database schema, relationships, indexes, and data access patterns.
Database Technology
Engine: SQLite3
Location: ~/.cpm/cpm.db (configurable)
Driver: github.com/mattn/go-sqlite3
Version: 3.x
Why SQLite?
- Zero Configuration: No separate database server required
- Embedded: Runs in-process with cpm
- Portable: Single file database, easy backup
- Reliable: ACID compliant, battle-tested
- Lightweight: Minimal memory and disk footprint
- Fast: Excellent performance for local operations
Database Schema
Entity-Relationship Diagram
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ organizations│◄────────┤ org_members │─────────►│ users │
│ │ │ │ │ │
│ - id │ │ - org_id (FK) │ │ - id │
│ - name │ │ - user_id (FK) │ │ - username │
│ - description│ │ - role │ │ - email │
│ - created_at │ │ │ │ - public_key │
└──────┬───────┘ └──────────────────┘ └──────┬───────┘
│ │
│ │
│ ┌──────────────────┐ │
└─────────────────►│ repositories │◄───────────────┘
│ │
│ - id │ (via repo_access)
│ - name │
│ - org_id (FK) │
│ - path │
│ - description │
│ - created_at │
└────────┬─────────┘
│
│
┌────────┴─────────┐
│ │
┌─────▼──────┐ ┌─────▼──────┐
│ org_repos │ │repo_access │
│ │ │ │
│- org_id │ │- repo_id │
│- repo_id │ │- user_id │
│ │ │- access_level
└────────────┘ └────────────┘
┌──────────────┐ ┌──────────────────┐
│ ssh_keys │ │ servers │
│ │ │ │
│ - id │◄─────────│ - id │
│ - name │ FK │ - name │
│ - public_key │ │ - host │
│ - private_ │ │ - port │
│ key_path │ │ - is_neighbor │
│ - created_at │ │ - ssh_key_id │
└──────────────┘ │ - created_at │
└──────────────────┘
Table Definitions
users
Stores user account information and SSH public keys.
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
public_key TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Columns:
id: Unique user identifierusername: Unique username (alphanumeric, hyphens, underscores)email: User email addresspublic_key: SSH public key for authenticationcreated_at: Account creation timestamp
Indexes:
- Primary key on
id - Unique constraint on
username
Relationships:
- One-to-many with
org_members - One-to-many with
repo_access
organizations
Stores organization definitions for grouping repositories.
CREATE TABLE IF NOT EXISTS organizations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Columns:
id: Unique organization identifiername: Organization name (unique)description: Optional organization descriptioncreated_at: Creation timestamp
Indexes:
- Primary key on
id - Unique constraint on
name
Relationships:
- One-to-many with
repositories - One-to-many with
org_members
repositories
Stores repository metadata and file system paths.
CREATE TABLE IF NOT EXISTS repositories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
org_id INTEGER NOT NULL,
path TEXT NOT NULL,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (org_id) REFERENCES organizations(id) ON DELETE CASCADE,
UNIQUE(org_id, name)
);
Columns:
id: Unique repository identifiername: Repository name (unique within organization)org_id: Organization ID (foreign key)path: Absolute file system path to bare repositorydescription: Optional repository descriptioncreated_at: Creation timestamp
Indexes:
- Primary key on
id - Index on
org_id - Unique constraint on
(org_id, name)
Relationships:
- Many-to-one with
organizations - One-to-many with
repo_access
ssh_keys
Stores SSH key pair information for authentication.
CREATE TABLE IF NOT EXISTS ssh_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
public_key TEXT NOT NULL,
private_key_path TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Columns:
id: Unique key identifiername: Key name for referencepublic_key: SSH public key contentprivate_key_path: Path to private key filecreated_at: Generation timestamp
Indexes:
- Primary key on
id - Unique constraint on
name
Relationships:
- One-to-many with
servers
servers
Stores remote server configurations.
CREATE TABLE IF NOT EXISTS servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
host TEXT NOT NULL,
port INTEGER NOT NULL DEFAULT 22,
is_neighbor BOOLEAN NOT NULL DEFAULT 0,
ssh_key_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ssh_key_id) REFERENCES ssh_keys(id) ON DELETE SET NULL
);
Columns:
id: Unique server identifiername: Server name for referencehost: Hostname or IP addressport: SSH port numberis_neighbor: Boolean flag for peer serversssh_key_id: Associated SSH key (foreign key)created_at: Registration timestamp
Indexes:
- Primary key on
id - Unique constraint on
name - Index on
is_neighbor
Relationships:
- Many-to-one with
ssh_keys
repo_access
Junction table for user-repository access permissions.
CREATE TABLE IF NOT EXISTS repo_access (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
access_level TEXT NOT NULL CHECK(access_level IN ('read', 'write', 'admin')),
FOREIGN KEY (repo_id) REFERENCES repositories(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(repo_id, user_id)
);
Columns:
id: Unique access record identifierrepo_id: Repository ID (foreign key)user_id: User ID (foreign key)access_level: Permission level (read, write, admin)
Indexes:
- Primary key on
id - Index on
repo_id - Index on
user_id - Unique constraint on
(repo_id, user_id)
Access Levels:
read: Read-only accesswrite: Read and write accessadmin: Full administrative access
Relationships:
- Many-to-one with
repositories - Many-to-one with
users
org_members
Junction table for organization membership with roles. (Implicit from schema analysis)
CREATE TABLE IF NOT EXISTS org_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
org_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
role TEXT NOT NULL CHECK(role IN ('owner', 'admin', 'member')),
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (org_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(org_id, user_id)
);
Roles:
owner: Full control including organization deletionadmin: Manage members and repositoriesmember: Read access to organization repositories
Data Access Layer
Database Connection
Location: internal/db/db.go
type DB struct {
conn *sql.DB
}
func Open(dbPath string) (*DB, error) {
conn, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}
// Initialize schema
if err := initSchema(conn); err != nil {
return nil, err
}
return &DB{conn: conn}, nil
}
CRUD Operations
Location: internal/db/crud.go
Repository Operations
// Create repository
func (db *DB) CreateRepository(name, path string, orgID int64) (int64, error)
// Get repository by ID
func (db *DB) GetRepository(id int64) (*Repository, error)
// Get repository by name and org
func (db *DB) GetRepositoryByName(name string, orgID int64) (*Repository, error)
// List all repositories
func (db *DB) ListRepositories() ([]Repository, error)
// List repositories by organization
func (db *DB) ListRepositoriesByOrg(orgID int64) ([]Repository, error)
// Update repository
func (db *DB) UpdateRepository(id int64, updates map[string]interface{}) error
// Delete repository
func (db *DB) DeleteRepository(id int64) error
User Operations
// Create user
func (db *DB) CreateUser(username, email, publicKey string) (int64, error)
// Get user by ID
func (db *DB) GetUser(id int64) (*User, error)
// Get user by username
func (db *DB) GetUserByUsername(username string) (*User, error)
// List all users
func (db *DB) ListUsers() ([]User, error)
// Update user
func (db *DB) UpdateUser(id int64, updates map[string]interface{}) error
// Delete user
func (db *DB) DeleteUser(id int64) error
Organization Operations
// Create organization
func (db *DB) CreateOrganization(name, description string) (int64, error)
// Get organization
func (db *DB) GetOrganization(id int64) (*Organization, error)
// List organizations
func (db *DB) ListOrganizations() ([]Organization, error)
// Add member to organization
func (db *DB) AddOrgMember(orgID, userID int64, role string) error
// Remove member from organization
func (db *DB) RemoveOrgMember(orgID, userID int64) error
// Delete organization
func (db *DB) DeleteOrganization(id int64) error
Data Models
Location: internal/db/models.go
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
PublicKey string `json:"public_key,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type Organization struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
type Repository struct {
ID int64 `json:"id"`
Name string `json:"name"`
OrgID int64 `json:"org_id"`
Path string `json:"path"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
type SSHKey struct {
ID int64 `json:"id"`
Name string `json:"name"`
PublicKey string `json:"public_key"`
PrivateKeyPath string `json:"private_key_path"`
CreatedAt time.Time `json:"created_at"`
}
type Server struct {
ID int64 `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
Port int `json:"port"`
IsNeighbor bool `json:"is_neighbor"`
SSHKeyID *int64 `json:"ssh_key_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
Query Patterns
Common Queries
Get repositories for user
func (db *DB) GetUserRepositories(userID int64) ([]Repository, error) {
query := `
SELECT DISTINCT r.*
FROM repositories r
LEFT JOIN org_members om ON r.org_id = om.org_id
LEFT JOIN repo_access ra ON r.id = ra.repo_id
WHERE om.user_id = ? OR ra.user_id = ?
`
// Execute query...
}
Get organization members with roles
func (db *DB) GetOrgMembers(orgID int64) ([]OrgMember, error) {
query := `
SELECT u.id, u.username, u.email, om.role, om.joined_at
FROM users u
JOIN org_members om ON u.id = om.user_id
WHERE om.org_id = ?
ORDER BY om.role, u.username
`
// Execute query...
}
Database Migrations
Schema Initialization
On first run, cpm creates all tables with proper indexes:
func initSchema(db *sql.DB) error {
_, err := db.Exec(schema)
return err
}
Future Migrations
Migration strategy for schema updates:
type Migration struct {
Version int
SQL string
}
var migrations = []Migration{
{1, "CREATE TABLE ..."},
{2, "ALTER TABLE ..."},
// Future migrations
}
Performance Optimization
Indexes
Strategic indexes for common query patterns:
-- Repository organization lookup
CREATE INDEX IF NOT EXISTS idx_repositories_org_id
ON repositories(org_id);
-- Repository access user lookup
CREATE INDEX IF NOT EXISTS idx_repo_access_repo_id
ON repo_access(repo_id);
-- Repository access repository lookup
CREATE INDEX IF NOT EXISTS idx_repo_access_user_id
ON repo_access(user_id);
-- Neighbor filtering
CREATE INDEX IF NOT EXISTS idx_servers_is_neighbor
ON servers(is_neighbor);
Query Optimization
- Use prepared statements
- Batch inserts where possible
- Appropriate index usage
- Limit result sets
- Use transactions for related operations
Data Integrity
Foreign Key Constraints
All foreign key relationships enforce referential integrity:
FOREIGN KEY (org_id) REFERENCES organizations(id) ON DELETE CASCADE
FOREIGN KEY (ssh_key_id) REFERENCES ssh_keys(id) ON DELETE SET NULL
Cascade Behavior
- ON DELETE CASCADE: Deleting organization removes repositories
- ON DELETE SET NULL: Deleting SSH key nullifies server reference
Unique Constraints
- Usernames must be unique
- Organization names must be unique
- Repository names unique within organization
- SSH key names must be unique
Backup and Recovery
Backup Strategy
# Simple file copy (when cpm not running)
cp ~/.cpm/cpm.db ~/.cpm/backups/cpm-$(date +%Y%m%d).db
# Online backup using SQLite backup API
sqlite3 ~/.cpm/cpm.db ".backup ~/.cpm/backups/cpm-$(date +%Y%m%d).db"
# Using cpm (future feature)
cpm db backup --output ~/backups/
Recovery
# Restore from backup
cp ~/.cpm/backups/cpm-20240115.db ~/.cpm/cpm.db
# Verify integrity
sqlite3 ~/.cpm/cpm.db "PRAGMA integrity_check;"