feat: add PostgreSQL persistence layer with Neon DB support

This commit is contained in:
user
2026-04-27 12:35:03 +09:00
parent 01d102f5ef
commit bfdc7399eb
12 changed files with 671 additions and 6 deletions

29
internal/db/db.go Normal file
View 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
View 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
}

View 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()
);