feat: add PostgreSQL persistence layer with Neon DB support
This commit is contained in:
29
internal/db/db.go
Normal file
29
internal/db/db.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func Open(databaseURL string) (*pgxpool.Pool, error) {
|
||||
if databaseURL == "" {
|
||||
return nil, fmt.Errorf("database URL is required")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pool, err := pgxpool.New(ctx, databaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create connection pool: %w", err)
|
||||
}
|
||||
|
||||
if err := pool.Ping(ctx); err != nil {
|
||||
return nil, fmt.Errorf("ping database: %w", err)
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
20
internal/db/migrate.go
Normal file
20
internal/db/migrate.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
//go:embed migrations/001_init.sql
|
||||
var initSQL string
|
||||
|
||||
func Migrate(pool *pgxpool.Pool) error {
|
||||
_, err := pool.Exec(context.Background(), initSQL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run migration: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
106
internal/db/migrations/001_init.sql
Normal file
106
internal/db/migrations/001_init.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
-- Tutor Platform Initial Schema
|
||||
|
||||
CREATE TABLE IF NOT EXISTS interview_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
target_role TEXT NOT NULL,
|
||||
stack JSONB NOT NULL DEFAULT '[]',
|
||||
interview_timeline TEXT NOT NULL,
|
||||
questions JSONB NOT NULL DEFAULT '[]',
|
||||
answers JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS learner_profiles (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
target_role TEXT NOT NULL,
|
||||
stack JSONB NOT NULL DEFAULT '[]',
|
||||
interview_timeline TEXT NOT NULL,
|
||||
preferences JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS learner_mastery (
|
||||
user_id TEXT NOT NULL,
|
||||
concept_id TEXT NOT NULL,
|
||||
concept_label TEXT NOT NULL,
|
||||
state TEXT NOT NULL DEFAULT 'unknown',
|
||||
evidence JSONB NOT NULL DEFAULT '[]',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (user_id, concept_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS learner_misconceptions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
concept JSONB NOT NULL DEFAULT '{}',
|
||||
description TEXT NOT NULL,
|
||||
evidence JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS learner_interventions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
concept JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS learner_review_schedules (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
concept JSONB NOT NULL DEFAULT '{}',
|
||||
due_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ontology_materials (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
source_type TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ontology_concepts (
|
||||
id TEXT PRIMARY KEY,
|
||||
material_id TEXT NOT NULL,
|
||||
concept_id TEXT NOT NULL,
|
||||
concept_label TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
review_state TEXT NOT NULL DEFAULT 'candidate',
|
||||
evidence JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ontology_edges (
|
||||
id TEXT PRIMARY KEY,
|
||||
from_concept_id TEXT NOT NULL,
|
||||
to_concept_id TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
evidence JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ontology_gaps (
|
||||
id TEXT PRIMARY KEY,
|
||||
concept_id TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
evidence JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teaching_asset_prompts (
|
||||
id TEXT PRIMARY KEY,
|
||||
concept_id TEXT NOT NULL,
|
||||
asset_type TEXT NOT NULL,
|
||||
prompt TEXT NOT NULL,
|
||||
model_key TEXT NOT NULL,
|
||||
review_state TEXT NOT NULL DEFAULT 'candidate',
|
||||
requires_model_id_verification BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
source_evidence JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
Reference in New Issue
Block a user