feat: P1 profile/portrait API and freeze local-dev environment
Add host-first environment contracts (Local vs CI vs Prod), deps-only compose, and the Profile → Portrait → deep-access mock payment slice with device identity and auto-migrate on API startup. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
DROP TABLE IF EXISTS deep_accesses;
|
||||
DROP TABLE IF EXISTS payments;
|
||||
DROP TABLE IF EXISTS orders;
|
||||
DROP TABLE IF EXISTS memberships;
|
||||
DROP TABLE IF EXISTS growth_reports;
|
||||
DROP TABLE IF EXISTS profiles;
|
||||
DROP TABLE IF EXISTS device_identities;
|
||||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF EXISTS schema_migrations;
|
||||
@@ -0,0 +1,105 @@
|
||||
-- P1 core schema (see .ai/domain/erd.md)
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version text PRIMARY KEY,
|
||||
applied_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
status varchar(32) NOT NULL DEFAULT 'active',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_identities (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
device_key varchar(128) NOT NULL UNIQUE,
|
||||
user_id uuid NULL REFERENCES users(id),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_device_identities_user_id ON device_identities(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES users(id),
|
||||
relation varchar(16) NOT NULL CHECK (relation IN ('self', 'other')),
|
||||
display_name varchar(64) NOT NULL DEFAULT '',
|
||||
birth_date date NOT NULL,
|
||||
birth_time time NULL,
|
||||
birth_place varchar(128) NULL,
|
||||
gender varchar(32) NULL,
|
||||
relation_type varchar(32) NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS growth_reports (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES users(id),
|
||||
profile_id uuid NOT NULL REFERENCES profiles(id),
|
||||
type varchar(32) NOT NULL,
|
||||
summary jsonb NOT NULL DEFAULT '{}',
|
||||
detail jsonb NOT NULL DEFAULT '{}',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_growth_reports_user_id ON growth_reports(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_growth_reports_profile_id ON growth_reports(profile_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memberships (
|
||||
user_id uuid PRIMARY KEY REFERENCES users(id),
|
||||
plan varchar(32) NOT NULL DEFAULT 'month',
|
||||
status varchar(32) NOT NULL DEFAULT 'expired',
|
||||
expires_at timestamptz NULL,
|
||||
ask_quota_left int NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES users(id),
|
||||
kind varchar(32) NOT NULL,
|
||||
plan varchar(32) NULL,
|
||||
report_id uuid NULL REFERENCES growth_reports(id),
|
||||
amount_cents int NOT NULL DEFAULT 0,
|
||||
status varchar(32) NOT NULL DEFAULT 'created',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
order_id uuid NOT NULL REFERENCES orders(id),
|
||||
channel varchar(32) NOT NULL DEFAULT 'mock',
|
||||
status varchar(32) NOT NULL DEFAULT 'created',
|
||||
raw jsonb NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_payments_order_id ON payments(order_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS deep_accesses (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES users(id),
|
||||
report_id uuid NOT NULL REFERENCES growth_reports(id),
|
||||
order_id uuid NOT NULL REFERENCES orders(id),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz NULL,
|
||||
UNIQUE (user_id, report_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_deep_accesses_report_id ON deep_accesses(report_id);
|
||||
Reference in New Issue
Block a user