diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f4f0c8..3e70e73 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,5 +25,6 @@ "onAutoForward": "ignore" }, "postCreateCommand": "initdb -D $PGDATA && pg_ctl -D $PGDATA -o '-k /run/postgresql' -l /tmp/pg.log start && createdb chocomax && pg_ctl -D $PGDATA stop", + "postStartCommand": "pg_ctl -D $PGDATA -o '-k /run/postgresql' -l /tmp/pg.log start", "remoteUser": "vscode" } diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index d533e66..f7eaac6 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -3,6 +3,8 @@ name: Create Release permissions: contents: write + pull-requests: read + statuses: read on: push: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4c6c2fb..dce8b21 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -9,7 +9,7 @@ on: branches: - main paths: - - database/** + - database/**/*.sql - .github/workflows/unit-tests.yml pull_request: null workflow_dispatch: diff --git a/Dockerfile b/Dockerfile index 67747bb..4e89688 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,9 @@ RUN chmod +x /usr/local/bin/*.sh && \ # Drop root privileges USER postgres +# Copy configuration files +COPY config/. /etc/postgresql/ + # Use tini for proper signal handling ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"] diff --git a/conf/postgresql.conf b/conf/postgresql.conf deleted file mode 100644 index fa816df..0000000 --- a/conf/postgresql.conf +++ /dev/null @@ -1 +0,0 @@ -listen_addresses = 'localhost' diff --git a/config/pg_hba.conf b/config/pg_hba.conf new file mode 100644 index 0000000..273b4d2 --- /dev/null +++ b/config/pg_hba.conf @@ -0,0 +1,17 @@ +# PostgreSQL Client Authentication Configuration File +# TYPE DATABASE USER ADDRESS METHOD + +# Allow local connections for all users with scram-sha-256 +local all all scram-sha-256 + +# Allow connections from any IP address with scram-sha-256 +# host all all 0.0.0.0/0 scram-sha-256 +# host all all ::/0 scram-sha-256 + +# "replication" privilege for streaming replication, by default only from localhost +# local replication all scram-sha-256 +# host replication all 127.0.0.1/32 scram-sha-256 +# host replication all ::1/128 scram-sha-256 + +# Allow only Docker bridge network +host all all 172.17.0.0/16 scram-sha-256 diff --git a/config/postgresql.conf b/config/postgresql.conf new file mode 100644 index 0000000..128ef1a --- /dev/null +++ b/config/postgresql.conf @@ -0,0 +1 @@ +listen_addresses = '*' diff --git a/database/functions/authenticate_user.sql b/database/functions/authenticate_user.sql index af8534f..f44ef0c 100644 --- a/database/functions/authenticate_user.sql +++ b/database/functions/authenticate_user.sql @@ -6,7 +6,7 @@ CREATE OR REPLACE FUNCTION authenticate_user( p_user_agent TEXT ) RETURNS BOOLEAN AS $$ DECLARE - v_user_id INTEGER; + v_user_id UUID; BEGIN -- Attempt to find the user by username and hashed password SELECT user_id INTO v_user_id diff --git a/database/functions/disable_2fa.sql b/database/functions/disable_2fa.sql index 0840da0..d5d4337 100644 --- a/database/functions/disable_2fa.sql +++ b/database/functions/disable_2fa.sql @@ -1,5 +1,5 @@ -- Disable 2FA for a user -CREATE OR REPLACE FUNCTION disable_2fa(p_user_id INTEGER) RETURNS VOID AS $$ +CREATE OR REPLACE FUNCTION disable_2fa(p_user_id UUID) RETURNS VOID AS $$ BEGIN UPDATE user_authentication_methods SET is_enabled = FALSE, updated_at = NOW() diff --git a/database/functions/get_user_2fa_secret.sql b/database/functions/get_user_2fa_secret.sql index 06d53b8..d13f1bc 100644 --- a/database/functions/get_user_2fa_secret.sql +++ b/database/functions/get_user_2fa_secret.sql @@ -1,5 +1,5 @@ -- Get the user's authentication methods secret -CREATE OR REPLACE FUNCTION get_user_authentication_method_secret(p_user_id INTEGER) RETURNS TABLE (method TEXT, secret TEXT) AS $$ +CREATE OR REPLACE FUNCTION get_user_authentication_method_secret(p_user_id UUID) RETURNS TABLE (method TEXT, secret TEXT) AS $$ BEGIN RETURN QUERY SELECT authentication_method, user_authentication_method_secret diff --git a/database/functions/is_2fa_enabled.sql b/database/functions/is_2fa_enabled.sql index 8188427..7ff9f93 100644 --- a/database/functions/is_2fa_enabled.sql +++ b/database/functions/is_2fa_enabled.sql @@ -1,5 +1,5 @@ -- Check if the user has 2FA enabled -CREATE OR REPLACE FUNCTION is_2fa_enabled(p_user_id INTEGER) RETURNS BOOLEAN AS $$ +CREATE OR REPLACE FUNCTION is_2fa_enabled(p_user_id UUID) RETURNS BOOLEAN AS $$ DECLARE v_enabled BOOLEAN; BEGIN diff --git a/database/procedures/handle_successful_login.sql b/database/procedures/handle_successful_login.sql index ffd2e8a..19a96dc 100644 --- a/database/procedures/handle_successful_login.sql +++ b/database/procedures/handle_successful_login.sql @@ -1,6 +1,6 @@ -- Update last_login and log successful attempt CREATE OR REPLACE PROCEDURE handle_successful_login( - p_user_id INTEGER, + p_user_id UUID, p_ip_address INET, p_user_agent TEXT ) diff --git a/database/procedures/log_login_attempt.sql b/database/procedures/log_login_attempt.sql index c8f7d07..cdcff7e 100644 --- a/database/procedures/log_login_attempt.sql +++ b/database/procedures/log_login_attempt.sql @@ -1,6 +1,6 @@ -- Log a login attempt (used in both success and failure cases) CREATE OR REPLACE PROCEDURE log_login_attempt( - p_user_id INTEGER, + p_user_id UUID, p_ip_address INET, p_user_agent TEXT, p_success BOOLEAN diff --git a/database/procedures/register_user.sql b/database/procedures/register_user.sql index 38c4acc..9a025eb 100644 --- a/database/procedures/register_user.sql +++ b/database/procedures/register_user.sql @@ -6,7 +6,7 @@ CREATE OR REPLACE PROCEDURE register_user( p_password_hash VARCHAR, p_phone_encrypted TEXT, p_phone_hash TEXT, - p_preferred_language VARCHAR + p_preferred_language_iso_code CHAR(2) ) LANGUAGE plpgsql AS $$ BEGIN @@ -18,7 +18,7 @@ BEGIN password_hash, phone_encrypted, phone_hash, - preferred_language + language_id ) VALUES ( p_email_encrypted, @@ -27,7 +27,7 @@ BEGIN p_password_hash, p_phone_encrypted, p_phone_hash, - p_preferred_language + (SELECT language_id FROM languages WHERE iso_code = p_preferred_language_iso_code) ); EXCEPTION WHEN unique_violation THEN diff --git a/database/schema.sql b/database/schema.sql index bbb750d..8561f1d 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -1,3 +1,5 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + -- Enums & Types CREATE TYPE delivery_type AS ENUM ('pickup', 'delivery'); @@ -31,79 +33,88 @@ CREATE TYPE promotion_type AS ENUM ('buy_x_get_y', 'bulk_discount'); CREATE TYPE admin_account_role AS ENUM ('admin', 'owner', 'moderator'); CREATE TYPE admin_action_target_type AS ENUM ('user', 'product', 'comment', 'order'); +-- Languages (i18n) - followed by translation tables later in the schema + +CREATE TABLE languages ( + language_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + iso_code CHAR(2) UNIQUE, + english_name VARCHAR(30) UNIQUE, + native_name VARCHAR(30) UNIQUE +); + -- User & Security Tables CREATE TABLE users ( - user_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(50) UNIQUE NOT NULL, email_encrypted TEXT NOT NULL, email_hash TEXT UNIQUE NOT NULL, + is_email_verified BOOLEAN DEFAULT FALSE, password_hash TEXT NOT NULL, phone_encrypted TEXT, phone_hash TEXT UNIQUE, - preferred_language TEXT DEFAULT 'en', - is_email_verified BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + language_id INTEGER REFERENCES languages (language_id) ON DELETE SET NULL, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, last_login_at TIMESTAMPTZ, deleted_at TIMESTAMPTZ ); CREATE TABLE user_permissions ( user_permission_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, permission TEXT NOT NULL, granted BOOLEAN DEFAULT TRUE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE user_sessions ( user_session_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, token TEXT NOT NULL UNIQUE, device_info TEXT, ip_address INET, expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE email_verifications ( email_verification_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, token TEXT NOT NULL UNIQUE, is_verified BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE password_resets ( password_reset_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, token TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE user_authentication_methods ( user_authentication_method_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, user_authentication_method_secret TEXT NOT NULL, -- Base32 encoded secret for TOTP is_enabled BOOLEAN DEFAULT FALSE, authentication_method AUTHENTICATION_METHOD NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE login_attempts ( login_attempt_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, + user_id UUID REFERENCES users (user_id) ON DELETE SET NULL, ip_address INET NOT NULL, user_agent TEXT NOT NULL, success BOOLEAN NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp ); -- Product & Interaction Tables @@ -115,16 +126,16 @@ CREATE TABLE products ( price NUMERIC(10, 2) NOT NULL CHECK (price >= 0), image_url TEXT, is_enabled BOOLEAN DEFAULT TRUE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE product_categories ( product_category_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, product_category_name TEXT NOT NULL UNIQUE, product_category_description TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE product_variants ( @@ -133,8 +144,8 @@ CREATE TABLE product_variants ( size TEXT, is_test BOOLEAN DEFAULT FALSE, price_override NUMERIC(10, 2) CHECK (price_override >= 0), - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE product_images ( @@ -143,48 +154,48 @@ CREATE TABLE product_images ( variant_id INTEGER REFERENCES product_variants (product_variant_id) ON DELETE SET NULL, image_url TEXT NOT NULL, is_primary BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE product_likes ( product_like_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, product_id INTEGER NOT NULL REFERENCES products (product_id) ON DELETE CASCADE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (user_id, product_id) ); CREATE TABLE product_comments ( product_comment_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, product_id INTEGER NOT NULL REFERENCES products (product_id) ON DELETE CASCADE, content TEXT NOT NULL, is_moderated BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE moderation_actions ( moderation_action_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, action_type MODERATION_ACTION_TYPE NOT NULL, target_id INTEGER NOT NULL, -- Could be user_id, product_id, comment_id, etc. target_type MODERATION_TARGET_TYPE NOT NULL, reason TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); -- Cart & Order Tables CREATE TABLE carts ( cart_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, + user_id UUID REFERENCES users (user_id) ON DELETE SET NULL, session_token TEXT UNIQUE, -- For anonymous carts - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE cart_items ( @@ -192,20 +203,20 @@ CREATE TABLE cart_items ( cart_id INTEGER NOT NULL REFERENCES carts (cart_id) ON DELETE CASCADE, product_variant_id INTEGER NOT NULL REFERENCES product_variants (product_variant_id) ON DELETE CASCADE, quantity INTEGER NOT NULL CHECK (quantity > 0), - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (cart_id, product_variant_id) ); CREATE TABLE orders ( order_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, + user_id UUID REFERENCES users (user_id) ON DELETE SET NULL, cart_id INTEGER NOT NULL REFERENCES carts (cart_id) ON DELETE CASCADE, delivery_type DELIVERY_TYPE NOT NULL, total_price NUMERIC(10, 2) NOT NULL CHECK (total_price >= 0), order_status ORDER_STATUS NOT NULL DEFAULT 'pending', - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE order_items ( @@ -214,8 +225,8 @@ CREATE TABLE order_items ( product_variant_id INTEGER NOT NULL REFERENCES product_variants (product_variant_id) ON DELETE CASCADE, quantity INTEGER NOT NULL CHECK (quantity > 0), price NUMERIC(10, 2) NOT NULL CHECK (price >= 0), - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (order_id, product_variant_id) ); @@ -223,20 +234,20 @@ CREATE TABLE order_status_histories ( order_status_history_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE, order_status_history ORDER_STATUS NOT NULL, - changed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + changed_at TIMESTAMPTZ DEFAULT current_timestamp, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE order_delivery_infos ( order_delivery_info_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE, delivery_address TEXT NOT NULL, - delivery_partner_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, -- Could be a delivery person or store + delivery_partner_id UUID REFERENCES users (user_id) ON DELETE SET NULL, -- Could be a delivery person or store delivery_type DELIVERY_TYPE NOT NULL, estimated_delivery_time TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE order_timestamps ( @@ -246,8 +257,8 @@ CREATE TABLE order_timestamps ( ready_at TIMESTAMPTZ, sent_at TIMESTAMPTZ, received_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); -- Promotions & Loyalty @@ -260,17 +271,17 @@ CREATE TABLE discount_codes ( min_order_value NUMERIC(10, 2) CHECK (min_order_value >= 0), max_uses INTEGER CHECK (max_uses > 0), expiration_date TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE user_discounts ( user_discount_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, discount_code_id INTEGER NOT NULL REFERENCES discount_codes (discount_code_id) ON DELETE CASCADE, - used_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + used_at TIMESTAMPTZ DEFAULT current_timestamp, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (user_id, discount_code_id) ); @@ -281,36 +292,29 @@ CREATE TABLE loyalty_programs ( condition TEXT NOT NULL, -- e.g., "5 of size M" reward TEXT NOT NULL, -- e.g., "1 free" is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE user_loyalty_progress ( user_loyalty_progress_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, product_id INTEGER NOT NULL REFERENCES products (product_id) ON DELETE CASCADE, quantity INTEGER NOT NULL CHECK (quantity >= 0), - last_updated TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + last_updated TIMESTAMPTZ DEFAULT current_timestamp, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); -- Internationalization (i18n) -CREATE TABLE languages ( - language_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - iso_code CHAR(2) UNIQUE, - english_name VARCHAR(30) UNIQUE, - native_name VARCHAR(30) UNIQUE -); - CREATE TABLE translations ( translation_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, translation_key VARCHAR(100) NOT NULL, language_id INTEGER NOT NULL REFERENCES languages (language_id) ON DELETE CASCADE, translation_value TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (translation_key, language_id) ); @@ -320,8 +324,8 @@ CREATE TABLE product_translations ( language_id INTEGER NOT NULL REFERENCES languages (language_id) ON DELETE CASCADE, product_name TEXT NOT NULL, product_description TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (product_id, language_id) ); @@ -331,8 +335,8 @@ CREATE TABLE category_translations ( language_id INTEGER NOT NULL REFERENCES languages (language_id) ON DELETE CASCADE, category_name TEXT NOT NULL, category_description TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (category_id, language_id) ); @@ -340,33 +344,33 @@ CREATE TABLE category_translations ( CREATE TABLE metrics_events ( metrics_event_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, + user_id UUID REFERENCES users (user_id) ON DELETE SET NULL, event_type TEXT NOT NULL, event_data JSONB NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE admin_accounts ( admin_account_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, admin_account_role ADMIN_ACCOUNT_ROLE NOT NULL, is_active BOOLEAN DEFAULT TRUE, target_type ADMIN_ACTION_TARGET_TYPE NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp, UNIQUE (user_id, admin_account_role) ); CREATE TABLE admin_actions ( admin_action_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, action_type TEXT NOT NULL, target_id INTEGER NOT NULL, -- Could be user_id, product_id, comment_id, etc. target_type TEXT NOT NULL CHECK (target_type IN ('user', 'product', 'comment', 'order')), details JSONB, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); -- Support & Feedback @@ -376,18 +380,18 @@ CREATE TABLE contact_messages ( contact_name TEXT NOT NULL, contact_email TEXT NOT NULL, content TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE TABLE feedbacks ( feedback_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - user_id INTEGER REFERENCES users (user_id) ON DELETE SET NULL, + user_id UUID REFERENCES users (user_id) ON DELETE SET NULL, product_id INTEGER NOT NULL REFERENCES products (product_id) ON DELETE CASCADE, rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5), comment TEXT, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT current_timestamp, + updated_at TIMESTAMPTZ DEFAULT current_timestamp ); CREATE INDEX idx_users_email_hash ON users (email_hash); diff --git a/database/tests/authentication.test.sql b/database/tests/authentication.test.sql index 7b9772c..1389eb3 100644 --- a/database/tests/authentication.test.sql +++ b/database/tests/authentication.test.sql @@ -10,7 +10,7 @@ DELETE FROM user_authentication_methods; DELETE FROM users; -- Create temp table to store test user ID -CREATE TEMP TABLE test_user (user_id INTEGER); +CREATE TEMP TABLE test_user (user_id UUID); -- Insert test user INSERT INTO users (username, email_encrypted, email_hash, password_hash) diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index f44e5e5..1e09b65 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -50,4 +50,13 @@ if [ ! -s "$PGDATA/PG_VERSION" ]; then fi echo "🚀 Starting PostgreSQL..." + +# Copy custom configs if they exist +if [ -f /etc/postgresql/postgresql.conf ]; then + cp /etc/postgresql/postgresql.conf "$PGDATA/postgresql.conf" +fi +if [ -f /etc/postgresql/pg_hba.conf ]; then + cp /etc/postgresql/pg_hba.conf "$PGDATA/pg_hba.conf" +fi + postgres -D "$PGDATA" -k /run/postgresql