formies/init.sql
Mohamad.Elsena a3236ae9d5 Refactor environment configuration for PostgreSQL and enhance application structure
- Updated `.env` and `.env.test` files to include PostgreSQL connection settings and Redis configuration.
- Migrated database from SQLite to PostgreSQL, updating relevant queries and connection logic.
- Enhanced error handling and logging throughout the application.
- Added new test utilities for PostgreSQL integration and updated user model methods.
- Introduced new routes for user authentication and form management, ensuring compatibility with the new database structure.
- Created login and registration views in EJS for user interaction.
2025-05-28 16:16:33 +02:00

147 lines
6.3 KiB
PL/PgSQL

-- init.sql for PostgreSQL
-- Attempt to create the database if it doesn't exist.
-- Note: CREATE DATABASE IF NOT EXISTS is not standard SQL for all clients.
-- This might need to be handled outside the script or by connecting to a default db like 'postgres' first.
-- For docker-entrypoint-initdb.d, this script is typically run after the DB specified by POSTGRES_DB is created.
-- Enable pgcrypto extension for gen_random_uuid() if not already enabled
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Users table for authentication and authorization
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
uuid UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
first_name VARCHAR(255) DEFAULT NULL,
last_name VARCHAR(255) DEFAULT NULL,
role VARCHAR(50) DEFAULT 'user' CHECK(role IN ('user', 'admin', 'super_admin')),
is_verified BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
verification_token TEXT DEFAULT NULL,
password_reset_token TEXT DEFAULT NULL,
password_reset_expires TIMESTAMPTZ NULL DEFAULT NULL,
last_login TIMESTAMPTZ NULL DEFAULT NULL,
failed_login_attempts INTEGER DEFAULT 0,
account_locked_until TIMESTAMPTZ NULL DEFAULT NULL,
must_change_password BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
-- Removed redundant UNIQUE constraints as they are already on id, uuid, email
);
CREATE INDEX IF NOT EXISTS idx_email ON users (email);
CREATE INDEX IF NOT EXISTS idx_verification_token ON users (verification_token);
CREATE INDEX IF NOT EXISTS idx_password_reset_token ON users (password_reset_token);
CREATE INDEX IF NOT EXISTS idx_uuid_users ON users (uuid);
-- User sessions table for JWT blacklisting and session management
CREATE TABLE IF NOT EXISTS user_sessions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
token_jti TEXT NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
user_agent TEXT DEFAULT NULL,
ip_address VARCHAR(255) DEFAULT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_token_jti ON user_sessions (token_jti);
CREATE INDEX IF NOT EXISTS idx_user_id_sessions ON user_sessions (user_id);
CREATE INDEX IF NOT EXISTS idx_expires_at_sessions ON user_sessions (expires_at);
-- Forms table
CREATE TABLE IF NOT EXISTS forms (
id SERIAL PRIMARY KEY,
uuid UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
user_id INTEGER NOT NULL,
name VARCHAR(255) DEFAULT 'My Form',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
thank_you_url TEXT DEFAULT NULL,
thank_you_message TEXT DEFAULT NULL,
ntfy_enabled BOOLEAN DEFAULT TRUE,
is_archived BOOLEAN DEFAULT FALSE,
allowed_domains TEXT DEFAULT NULL, -- Consider array of VARCHARs or separate table for multi-domain
email_notifications_enabled BOOLEAN NOT NULL DEFAULT FALSE,
notification_email_address VARCHAR(255) DEFAULT NULL,
recaptcha_enabled BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_user_id_forms ON forms (user_id);
CREATE INDEX IF NOT EXISTS idx_uuid_forms ON forms (uuid);
-- Submissions table
CREATE TABLE IF NOT EXISTS submissions (
id SERIAL PRIMARY KEY,
form_uuid UUID NOT NULL,
user_id INTEGER NOT NULL, -- Assuming submissions are tied to a user account that owns the form
data JSONB NOT NULL, -- Storing JSON as JSONB
ip_address VARCHAR(255) NULL,
submitted_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (form_uuid) REFERENCES forms(uuid) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -- Or remove if submissions are anonymous to users table
);
CREATE INDEX IF NOT EXISTS idx_form_uuid_submissions ON submissions (form_uuid);
CREATE INDEX IF NOT EXISTS idx_user_id_submissions ON submissions (user_id);
CREATE INDEX IF NOT EXISTS idx_submitted_at_submissions ON submissions (submitted_at);
-- Rate limiting table
CREATE TABLE IF NOT EXISTS rate_limits (
id SERIAL PRIMARY KEY,
identifier TEXT NOT NULL,
action TEXT NOT NULL,
count INTEGER DEFAULT 1,
window_start TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL,
UNIQUE (identifier, action)
);
CREATE INDEX IF NOT EXISTS idx_identifier_action_rate_limits ON rate_limits (identifier, action);
CREATE INDEX IF NOT EXISTS idx_expires_at_rate_limits ON rate_limits (expires_at);
-- API Keys table
CREATE TABLE IF NOT EXISTS api_keys (
id SERIAL PRIMARY KEY,
uuid UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
user_id INTEGER NOT NULL,
key_name VARCHAR(255) DEFAULT NULL,
api_key_identifier TEXT NOT NULL UNIQUE,
hashed_api_key_secret TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMPTZ NULL DEFAULT NULL,
expires_at TIMESTAMPTZ NULL DEFAULT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_user_id_api_keys ON api_keys (user_id);
CREATE INDEX IF NOT EXISTS idx_api_key_identifier_api_keys ON api_keys (api_key_identifier);
-- Function and Trigger to update 'updated_at' timestamp
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger for users table
CREATE TRIGGER set_timestamp_users
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_timestamp();
-- Trigger for forms table
CREATE TRIGGER set_timestamp_forms
BEFORE UPDATE ON forms
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_timestamp();
-- Create default super admin user
-- Using ON CONFLICT to prevent error if user already exists.
-- UUID is now generated by default by the database.
INSERT INTO users (email, password_hash, first_name, last_name, role, is_verified, is_active, must_change_password)
VALUES ('admin@formies.local', 'NEEDS_TO_BE_SET_ON_FIRST_LOGIN', 'Admin', 'User', 'super_admin', TRUE, TRUE, TRUE)
ON CONFLICT (email) DO NOTHING;
-- Note: PRAGMA foreign_keys = ON; is not needed in PostgreSQL. FKs are enforced by default if defined.
-- Note: Backticks for table/column names are generally not needed unless using reserved words or special chars.
-- Standard SQL double quotes can be used if necessary, but unquoted is often preferred.