diff --git a/db/migrations/000001_init_schema.down.sql b/db/migrations/000001_init_schema.down.sql deleted file mode 100644 index ea4da0f..0000000 --- a/db/migrations/000001_init_schema.down.sql +++ /dev/null @@ -1,3 +0,0 @@ -DROP TABLE IF EXISTS user_roles; -DROP TABLE IF EXISTS roles; -DROP TABLE IF EXISTS users; diff --git a/db/migrations/000001_init_schema.up.sql b/db/migrations/000001_init_schema.up.sql deleted file mode 100644 index abd1981..0000000 --- a/db/migrations/000001_init_schema.up.sql +++ /dev/null @@ -1,121 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS postgis; - --- CREATE EXTENSION IF NOT EXISTS pgcrypto; - -CREATE TABLE IF NOT EXISTS users ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - email TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - google_id VARCHAR(255) UNIQUE, - auth_provider VARCHAR(50) NOT NULL DEFAULT 'local', - is_verified BOOLEAN NOT NULL DEFAULT false, - is_deleted BOOLEAN NOT NULL DEFAULT false, - token_version INT NOT NULL DEFAULT 1, - refresh_token TEXT, - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE user_profiles ( - user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - display_name TEXT, - full_name TEXT, - avatar_url TEXT, - bio TEXT, - location TEXT, - website TEXT, - country_code CHAR(2), - phone TEXT, - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE user_verifications ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - verify_type SMALLINT NOT NULL, -- 1 = ID_CARD, 2 = EDUCATION, 3 = EXPERT - document_url TEXT NOT NULL, - status SMALLINT NOT NULL DEFAULT 1, -- 1 pending, 2 approved, 3 rejected - reviewed_by UUID REFERENCES users(id), - reviewed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS roles ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - name TEXT UNIQUE NOT NULL, - is_deleted BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS user_roles ( - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - role_id UUID REFERENCES roles(id) ON DELETE CASCADE, - PRIMARY KEY (user_id, role_id) -); - -CREATE INDEX idx_users_active_created_at -ON users (created_at DESC) -WHERE is_deleted = false; - -CREATE INDEX idx_users_email_active -ON users (email) -WHERE is_deleted = false; - -CREATE INDEX idx_users_active_verified -ON users (is_active, is_verified) -WHERE is_deleted = false; - -CREATE INDEX idx_user_roles_user_id -ON user_roles (user_id); - -CREATE INDEX idx_user_roles_role_id -ON user_roles (role_id); - -CREATE INDEX idx_roles_active -ON roles (name) -WHERE is_deleted = false; - -INSERT INTO roles (name) VALUES - ('USER'), - ('ADMIN'), - ('MOD'), - ('HISTORIAN'), - ('BANNED') -ON CONFLICT (name) DO NOTHING; - -CREATE OR REPLACE FUNCTION update_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER trigger_users_updated_at - BEFORE UPDATE ON users - FOR EACH ROW - EXECUTE FUNCTION update_updated_at(); - - -CREATE TABLE IF NOT EXISTS user_tokens ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - token VARCHAR(255) NOT NULL UNIQUE, - token_type SMALLINT NOT NULL, - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ DEFAULT now() -); - -CREATE INDEX idx_user_tokens_token -ON user_tokens(token); - -CREATE INDEX idx_user_tokens_user_id -ON user_tokens(user_id); - -CREATE INDEX idx_user_tokens_type -ON user_tokens(token_type); - -CREATE INDEX idx_user_tokens_expires_at -ON user_tokens(expires_at); \ No newline at end of file diff --git a/db/migrations/000001_users.down.sql b/db/migrations/000001_users.down.sql new file mode 100644 index 0000000..c99ddcd --- /dev/null +++ b/db/migrations/000001_users.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS users; diff --git a/db/migrations/000001_users.up.sql b/db/migrations/000001_users.up.sql new file mode 100644 index 0000000..0e7fab6 --- /dev/null +++ b/db/migrations/000001_users.up.sql @@ -0,0 +1,40 @@ +CREATE EXTENSION IF NOT EXISTS postgis; + +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + google_id VARCHAR(255) UNIQUE, + auth_provider VARCHAR(50) NOT NULL DEFAULT 'local', + is_verified BOOLEAN NOT NULL DEFAULT false, + is_deleted BOOLEAN NOT NULL DEFAULT false, + token_version INT NOT NULL DEFAULT 1, + refresh_token TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX idx_users_active_created_at +ON users (created_at DESC) +WHERE is_deleted = false; + +CREATE INDEX idx_users_email_active +ON users (email) +WHERE is_deleted = false; + +CREATE INDEX idx_users_verified +ON users (is_verified) +WHERE is_deleted = false; + +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_users_updated_at + BEFORE UPDATE ON users + FOR EACH ROW + EXECUTE FUNCTION update_updated_at(); diff --git a/db/migrations/000002_profiles.down.sql b/db/migrations/000002_profiles.down.sql new file mode 100644 index 0000000..b48a59a --- /dev/null +++ b/db/migrations/000002_profiles.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS user_profiles; diff --git a/db/migrations/000002_profiles.up.sql b/db/migrations/000002_profiles.up.sql new file mode 100644 index 0000000..dd93458 --- /dev/null +++ b/db/migrations/000002_profiles.up.sql @@ -0,0 +1,29 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +CREATE TABLE IF NOT EXISTS user_profiles ( + user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + display_name TEXT, + full_name TEXT, + avatar_url TEXT, + bio TEXT, + location TEXT, + website TEXT, + country_code CHAR(2), + phone TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX idx_user_profiles_display_name +ON user_profiles USING gin (display_name gin_trgm_ops); + +CREATE INDEX idx_user_profiles_country +ON user_profiles(country_code); + +CREATE UNIQUE INDEX idx_user_profiles_phone +ON user_profiles(phone); + +CREATE TRIGGER trigger_user_profiles_updated_at +BEFORE UPDATE ON user_profiles +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); \ No newline at end of file diff --git a/db/migrations/000003_roles.down.sql b/db/migrations/000003_roles.down.sql new file mode 100644 index 0000000..14c8ac3 --- /dev/null +++ b/db/migrations/000003_roles.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS user_roles; +DROP TABLE IF EXISTS roles; \ No newline at end of file diff --git a/db/migrations/000003_roles.up.sql b/db/migrations/000003_roles.up.sql new file mode 100644 index 0000000..a0aec47 --- /dev/null +++ b/db/migrations/000003_roles.up.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS roles ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + name TEXT UNIQUE NOT NULL, + is_deleted BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS user_roles ( + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + role_id UUID REFERENCES roles(id) ON DELETE CASCADE, + PRIMARY KEY (user_id, role_id) +); + +CREATE INDEX idx_user_roles_user_id +ON user_roles (user_id); + +CREATE INDEX idx_user_roles_role_id +ON user_roles (role_id); + +CREATE INDEX idx_roles_active +ON roles (name) +WHERE is_deleted = false; + +INSERT INTO roles (name) VALUES + ('USER'), + ('ADMIN'), + ('MOD'), + ('HISTORIAN'), + ('BANNED') +ON CONFLICT (name) DO NOTHING; + +CREATE TRIGGER trigger_roles_updated_at +BEFORE UPDATE ON roles +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); \ No newline at end of file diff --git a/db/migrations/000004_verifications.down.sql b/db/migrations/000004_verifications.down.sql new file mode 100644 index 0000000..60ae462 --- /dev/null +++ b/db/migrations/000004_verifications.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS user_verifications; \ No newline at end of file diff --git a/db/migrations/000004_verifications.up.sql b/db/migrations/000004_verifications.up.sql new file mode 100644 index 0000000..ad0fe82 --- /dev/null +++ b/db/migrations/000004_verifications.up.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS user_verifications ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + verify_type SMALLINT NOT NULL, -- 1 = ID_CARD, 2 = EDUCATION, 3 = EXPERT + document_url TEXT NOT NULL, + status SMALLINT NOT NULL DEFAULT 1, -- 1 pending, 2 approved, 3 rejected + reviewed_by UUID REFERENCES users(id), + reviewed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT now() +); + + +CREATE INDEX idx_user_verifications_user_id ON user_verifications(user_id); +CREATE INDEX idx_user_verifications_user_type ON user_verifications(user_id, verify_type); +CREATE INDEX idx_user_verifications_status ON user_verifications(status); + +CREATE INDEX idx_user_verifications_status_created +ON user_verifications(status, created_at DESC); \ No newline at end of file diff --git a/db/migrations/000005_tokens.down.sql b/db/migrations/000005_tokens.down.sql new file mode 100644 index 0000000..1eed838 --- /dev/null +++ b/db/migrations/000005_tokens.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS user_tokens; \ No newline at end of file diff --git a/db/migrations/000005_tokens.up.sql b/db/migrations/000005_tokens.up.sql new file mode 100644 index 0000000..9b13a11 --- /dev/null +++ b/db/migrations/000005_tokens.up.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS user_tokens ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token VARCHAR(255) NOT NULL UNIQUE, + token_type SMALLINT NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX idx_user_tokens_token +ON user_tokens(token); + +CREATE INDEX idx_user_tokens_user_id +ON user_tokens(user_id); + +CREATE INDEX idx_user_tokens_type +ON user_tokens(token_type); + +CREATE INDEX idx_user_tokens_expires_at +ON user_tokens(expires_at); \ No newline at end of file diff --git a/db/migrations/000006_entities.down.sql b/db/migrations/000006_entities.down.sql new file mode 100644 index 0000000..f29ac13 --- /dev/null +++ b/db/migrations/000006_entities.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS entity_types; +DROP TABLE IF EXISTS entities; \ No newline at end of file diff --git a/db/migrations/000006_entities.up.sql b/db/migrations/000006_entities.up.sql new file mode 100644 index 0000000..b6e4072 --- /dev/null +++ b/db/migrations/000006_entities.up.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS entity_types ( + id SMALLSERIAL PRIMARY KEY, + name TEXT UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS entities ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + type_id SMALLINT REFERENCES entity_types(id), + name TEXT NOT NULL, + slug TEXT UNIQUE, + description TEXT, + thumbnail_url TEXT, + status SMALLINT DEFAULT 1, -- 1 draft, 2 published + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE INDEX idx_entities_slug ON entities(slug); + +CREATE TRIGGER trigger_entities_updated_at +BEFORE UPDATE ON entities +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); \ No newline at end of file diff --git a/db/migrations/000007_wiki.down.sql b/db/migrations/000007_wiki.down.sql new file mode 100644 index 0000000..c920df3 --- /dev/null +++ b/db/migrations/000007_wiki.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS wiki_pages; +DROP TABLE IF EXISTS wiki_versions; \ No newline at end of file diff --git a/db/migrations/000007_wiki.up.sql b/db/migrations/000007_wiki.up.sql new file mode 100644 index 0000000..6984dfd --- /dev/null +++ b/db/migrations/000007_wiki.up.sql @@ -0,0 +1,25 @@ +CREATE TABLE IF NOT EXISTS wiki_pages ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + entity_id UUID REFERENCES entities(id) ON DELETE CASCADE, + title TEXT, + content TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS wiki_versions ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + wiki_id UUID REFERENCES wiki_pages(id) ON DELETE CASCADE, + created_user UUID REFERENCES users(id), + note TEXT, + content TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + approved_at TIMESTAMPTZ +); + +CREATE INDEX idx_wiki_entity ON wiki_pages(entity_id); + +CREATE TRIGGER trigger_wiki_pages_updated_at +BEFORE UPDATE ON wiki_pages +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); \ No newline at end of file diff --git a/db/migrations/000008_geometries.down.sql b/db/migrations/000008_geometries.down.sql new file mode 100644 index 0000000..1cf7dc4 --- /dev/null +++ b/db/migrations/000008_geometries.down.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS geometries; +DROP TABLE IF EXISTS geo_versions; +DROP TABLE IF EXISTS entity_geometries; \ No newline at end of file diff --git a/db/migrations/000008_geometries.up.sql b/db/migrations/000008_geometries.up.sql new file mode 100644 index 0000000..2c8aedd --- /dev/null +++ b/db/migrations/000008_geometries.up.sql @@ -0,0 +1,33 @@ +CREATE TABLE IF NOT EXISTS geometries ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + geom GEOMETRY, -- point / polygon / line + time_start INT, + time_end INT, + bbox GEOMETRY, -- optional + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS geo_versions ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + geo_id UUID REFERENCES geometries(id) ON DELETE CASCADE, + created_user UUID REFERENCES users(id), + geom GEOMETRY, + note TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + approved_at TIMESTAMPTZ +); + +CREATE TABLE IF NOT EXISTS entity_geometries ( + entity_id UUID REFERENCES entities(id) ON DELETE CASCADE, + geometry_id UUID REFERENCES geometries(id) ON DELETE CASCADE, + PRIMARY KEY (entity_id, geometry_id) +); + +CREATE INDEX idx_geo_time ON geometries(time_start, time_end); +CREATE INDEX idx_geom_spatial ON geometries USING GIST (geom); + +CREATE TRIGGER trigger_geometries_updated_at +BEFORE UPDATE ON geometries +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); \ No newline at end of file