-- +goose Up -- Generate UUID v7 (time-ordered, millisecond precision). -- Structure: 48-bit unix_ts_ms | 4-bit version (0111) | 12-bit rand_a | 2-bit variant (10) | 62-bit rand_b CREATE OR REPLACE FUNCTION uuid_generate_v7() RETURNS uuid AS $$ DECLARE unix_ts_ms bytea; uuid_bytes bytea; BEGIN -- 48-bit Unix timestamp in milliseconds. -- int8send produces 8 big-endian bytes; skip the first 2 zero bytes to get 6. unix_ts_ms = substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3); -- Use a random v4 UUID as the source of random bits for rand_a and rand_b. uuid_bytes = uuid_send(gen_random_uuid()); -- Overwrite bytes 0–5 with the timestamp (positions 1–6 in 1-indexed bytea). uuid_bytes = overlay(uuid_bytes placing unix_ts_ms from 1 for 6); -- Set version nibble (bits 48–51) to 0111 (7). uuid_bytes = set_bit(uuid_bytes, 48, 0); uuid_bytes = set_bit(uuid_bytes, 49, 1); uuid_bytes = set_bit(uuid_bytes, 50, 1); uuid_bytes = set_bit(uuid_bytes, 51, 1); -- Variant bits (64–65) stay at 10 as inherited from gen_random_uuid(). RETURN encode(uuid_bytes, 'hex')::uuid; END $$ LANGUAGE plpgsql VOLATILE; CREATE TYPE user_plan AS ENUM ('free', 'paid'); CREATE TYPE user_gender AS ENUM ('male', 'female'); CREATE TYPE user_goal AS ENUM ('lose', 'maintain', 'gain'); CREATE TYPE activity_level AS ENUM ('low', 'moderate', 'high'); CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), firebase_uid VARCHAR(128) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL DEFAULT '', avatar_url TEXT, -- Body parameters height_cm SMALLINT, weight_kg DECIMAL(5,2), age SMALLINT, gender user_gender, activity activity_level, -- Goal and calculated daily norm goal user_goal, daily_calories INTEGER, -- Plan plan user_plan NOT NULL DEFAULT 'free', -- Preferences (JSONB for flexibility) preferences JSONB NOT NULL DEFAULT '{}'::jsonb, -- Refresh token refresh_token TEXT, token_expires_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_users_firebase_uid ON users (firebase_uid); CREATE INDEX idx_users_email ON users (email); -- +goose Down DROP TABLE IF EXISTS users; DROP TYPE IF EXISTS activity_level; DROP TYPE IF EXISTS user_goal; DROP TYPE IF EXISTS user_gender; DROP TYPE IF EXISTS user_plan; DROP FUNCTION IF EXISTS uuid_generate_v7();