chore: initial commit of ai site platform
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
206
platform/internal/meta/postgres.go
Normal file
206
platform/internal/meta/postgres.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"aijianzhan/platform/internal/blueprint"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type PostgresStore struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgresStore(db *sql.DB) *PostgresStore {
|
||||
return &PostgresStore{DB: db}
|
||||
}
|
||||
|
||||
func (s *PostgresStore) GetBySlug(ctx context.Context, tenantID int64, slug string) (*AppRecord, error) {
|
||||
const q = `
|
||||
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
|
||||
blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
|
||||
FROM platform_meta.tenant_apps
|
||||
WHERE tenant_id = $1 AND slug = $2
|
||||
LIMIT 1`
|
||||
row := s.DB.QueryRowContext(ctx, q, tenantID, slug)
|
||||
rec, err := scanApp(row)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("app not found")
|
||||
}
|
||||
return rec, err
|
||||
}
|
||||
|
||||
func (s *PostgresStore) FindPublishedBySlug(ctx context.Context, slug string) (*AppRecord, error) {
|
||||
const q = `
|
||||
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
|
||||
blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
|
||||
FROM platform_meta.tenant_apps
|
||||
WHERE slug = $1 AND status = $2
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1`
|
||||
row := s.DB.QueryRowContext(ctx, q, slug, string(StatusPublished))
|
||||
rec, err := scanApp(row)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("app not found")
|
||||
}
|
||||
return rec, err
|
||||
}
|
||||
|
||||
func (s *PostgresStore) ListByTenant(ctx context.Context, tenantID int64) ([]AppSummary, error) {
|
||||
const q = `
|
||||
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
|
||||
blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
|
||||
FROM platform_meta.tenant_apps
|
||||
WHERE tenant_id = $1
|
||||
ORDER BY updated_at DESC`
|
||||
rows, err := s.DB.QueryContext(ctx, q, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make([]AppSummary, 0)
|
||||
for rows.Next() {
|
||||
rec, err := scanApp(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, summarizeApp(rec))
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *PostgresStore) Save(ctx context.Context, app *AppRecord) error {
|
||||
if app == nil {
|
||||
return fmt.Errorf("app is nil")
|
||||
}
|
||||
if app.Blueprint == nil {
|
||||
return fmt.Errorf("blueprint is nil")
|
||||
}
|
||||
bpRaw, err := json.Marshal(app.Blueprint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ddlRaw, err := json.Marshal(app.DDL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
epRaw, err := json.Marshal(app.Endpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.CreatedAt.IsZero() {
|
||||
app.CreatedAt = time.Now().UTC()
|
||||
}
|
||||
app.UpdatedAt = time.Now().UTC()
|
||||
|
||||
const q = `
|
||||
INSERT INTO platform_meta.tenant_apps (
|
||||
app_id, tenant_id, slug, name, schema_name, database_name, engine, status,
|
||||
blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
|
||||
) VALUES (
|
||||
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14
|
||||
)
|
||||
ON CONFLICT (tenant_id, slug) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
schema_name = EXCLUDED.schema_name,
|
||||
database_name = EXCLUDED.database_name,
|
||||
engine = EXCLUDED.engine,
|
||||
status = EXCLUDED.status,
|
||||
blueprint_json = EXCLUDED.blueprint_json,
|
||||
ddl_json = EXCLUDED.ddl_json,
|
||||
endpoints_json = EXCLUDED.endpoints_json,
|
||||
error_msg = EXCLUDED.error_msg,
|
||||
updated_at = EXCLUDED.updated_at,
|
||||
app_id = platform_meta.tenant_apps.app_id,
|
||||
created_at = platform_meta.tenant_apps.created_at
|
||||
RETURNING app_id, created_at, updated_at`
|
||||
|
||||
err = s.DB.QueryRowContext(ctx, q,
|
||||
app.AppID,
|
||||
app.TenantID,
|
||||
app.Slug,
|
||||
app.Name,
|
||||
app.SchemaName,
|
||||
app.DatabaseName,
|
||||
app.Engine,
|
||||
string(app.Status),
|
||||
bpRaw,
|
||||
ddlRaw,
|
||||
epRaw,
|
||||
app.Error,
|
||||
app.CreatedAt,
|
||||
app.UpdatedAt,
|
||||
).Scan(&app.AppID, &app.CreatedAt, &app.UpdatedAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("meta save: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) {
|
||||
app, err := s.GetBySlug(ctx, tenantID, slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resolveResource(app, resource)
|
||||
}
|
||||
|
||||
type scannable interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func scanApp(row scannable) (*AppRecord, error) {
|
||||
var (
|
||||
rec AppRecord
|
||||
status string
|
||||
bpRaw, ddlRaw, epRaw []byte
|
||||
errMsg sql.NullString
|
||||
)
|
||||
err := row.Scan(
|
||||
&rec.AppID,
|
||||
&rec.TenantID,
|
||||
&rec.Slug,
|
||||
&rec.Name,
|
||||
&rec.SchemaName,
|
||||
&rec.DatabaseName,
|
||||
&rec.Engine,
|
||||
&status,
|
||||
&bpRaw,
|
||||
&ddlRaw,
|
||||
&epRaw,
|
||||
&errMsg,
|
||||
&rec.CreatedAt,
|
||||
&rec.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rec.Status = AppStatus(status)
|
||||
if errMsg.Valid {
|
||||
rec.Error = errMsg.String
|
||||
}
|
||||
var bp blueprint.Blueprint
|
||||
if err := json.Unmarshal(bpRaw, &bp); err != nil {
|
||||
return nil, fmt.Errorf("blueprint json: %w", err)
|
||||
}
|
||||
rec.Blueprint = &bp
|
||||
if len(ddlRaw) > 0 {
|
||||
_ = json.Unmarshal(ddlRaw, &rec.DDL)
|
||||
}
|
||||
if len(epRaw) > 0 {
|
||||
_ = json.Unmarshal(epRaw, &rec.Endpoints)
|
||||
}
|
||||
return &rec, nil
|
||||
}
|
||||
|
||||
// IsUniqueViolation 便于上层识别冲突(预留)。
|
||||
func IsUniqueViolation(err error) bool {
|
||||
var pqErr *pq.Error
|
||||
return errors.As(err, &pqErr) && pqErr.Code == "23505"
|
||||
}
|
||||
Reference in New Issue
Block a user