576 lines
31 KiB
SQL
576 lines
31 KiB
SQL
-- Initial embedded Turso schema. Every statement is safe to run repeatedly.
|
|
CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, login_username TEXT, password_hash TEXT, discord_id TEXT UNIQUE, role TEXT NOT NULL DEFAULT 'user', created_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
display_name TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
INSERT OR IGNORE INTO user_profiles(user_id,display_name,updated_at) SELECT id,username,created_at FROM users;
|
|
CREATE TABLE IF NOT EXISTS user_avatars (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL CHECK(source IN ('preset', 'upload')),
|
|
value TEXT NOT NULL,
|
|
byte_size INTEGER CHECK(byte_size IS NULL OR byte_size >= 0),
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS avatar_reviews (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
avatar_updated_at TEXT NOT NULL,
|
|
storage_key TEXT NOT NULL,
|
|
byte_size INTEGER NOT NULL,
|
|
status TEXT NOT NULL CHECK(status IN ('pending', 'approved', 'removed', 'withdrawn', 'superseded')),
|
|
submitted_at TEXT NOT NULL,
|
|
reviewed_at TEXT,
|
|
reviewed_by TEXT REFERENCES users(id),
|
|
review_note TEXT
|
|
);
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
category TEXT NOT NULL CHECK(category IN ('isac', 'contributions', 'comments', 'builds', 'guides', 'system')),
|
|
source TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
link TEXT,
|
|
mandatory INTEGER NOT NULL DEFAULT 0 CHECK(mandatory IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
read_at TEXT,
|
|
emailed_at TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS notifications_user_created_idx ON notifications(user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS notifications_user_unread_idx ON notifications(user_id, read_at, created_at DESC);
|
|
CREATE TABLE IF NOT EXISTS notification_preferences (
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
category TEXT NOT NULL CHECK(category IN ('isac', 'contributions', 'comments', 'builds', 'guides', 'system')),
|
|
in_app_enabled INTEGER NOT NULL DEFAULT 1 CHECK(in_app_enabled IN (0, 1)),
|
|
browser_enabled INTEGER NOT NULL DEFAULT 0 CHECK(browser_enabled IN (0, 1)),
|
|
email_frequency TEXT NOT NULL DEFAULT 'none' CHECK(email_frequency IN ('none', 'immediate', 'daily')),
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY(user_id, category)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS avatar_reviews_status_submitted_idx ON avatar_reviews(status, submitted_at DESC);
|
|
CREATE INDEX IF NOT EXISTS avatar_reviews_user_submitted_idx ON avatar_reviews(user_id, submitted_at DESC);
|
|
CREATE TABLE IF NOT EXISTS builds (id TEXT PRIMARY KEY, owner_id TEXT NOT NULL REFERENCES users(id), title TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, visibility TEXT NOT NULL DEFAULT 'draft', summary TEXT, guide_body TEXT, source_build_id TEXT REFERENCES builds(id), created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS build_items (id TEXT PRIMARY KEY, build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE, slot_id TEXT NOT NULL, item_definition_id TEXT NOT NULL, configuration_json TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS ratings (user_id TEXT NOT NULL REFERENCES users(id), build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE, value INTEGER NOT NULL CHECK(value BETWEEN 1 AND 5), PRIMARY KEY(user_id, build_id));
|
|
CREATE TABLE IF NOT EXISTS comments (id TEXT PRIMARY KEY, build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users(id), body TEXT NOT NULL, created_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS favorites (user_id TEXT NOT NULL REFERENCES users(id), build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE, created_at TEXT NOT NULL, PRIMARY KEY(user_id, build_id));
|
|
CREATE TABLE IF NOT EXISTS equipment_types (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, category TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS stat_definitions (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, value_kind TEXT NOT NULL, min_value REAL, max_value REAL, unit TEXT, active INTEGER NOT NULL DEFAULT 1);
|
|
CREATE TABLE IF NOT EXISTS stat_equipment_types (stat_id TEXT NOT NULL REFERENCES stat_definitions(id), equipment_type_id TEXT NOT NULL REFERENCES equipment_types(id), PRIMARY KEY(stat_id, equipment_type_id));
|
|
CREATE TABLE IF NOT EXISTS audit_log (id TEXT PRIMARY KEY, actor_id TEXT NOT NULL REFERENCES users(id), entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, action TEXT NOT NULL, before_json TEXT, after_json TEXT, created_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS roles (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT);
|
|
CREATE TABLE IF NOT EXISTS permissions (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT);
|
|
CREATE TABLE IF NOT EXISTS user_roles (user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY(user_id, role_id));
|
|
CREATE TABLE IF NOT EXISTS role_permissions (role_id TEXT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, permission_id TEXT NOT NULL REFERENCES permissions(id) ON DELETE CASCADE, scope_json TEXT, PRIMARY KEY(role_id, permission_id));
|
|
CREATE TABLE IF NOT EXISTS user_permissions (user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, permission_id TEXT NOT NULL REFERENCES permissions(id) ON DELETE CASCADE, PRIMARY KEY(user_id, permission_id));
|
|
CREATE TABLE IF NOT EXISTS user_status (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
last_active_at TEXT,
|
|
blocked_at TEXT,
|
|
blocked_by TEXT REFERENCES users(id),
|
|
banned_until TEXT,
|
|
moderation_reason TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS login_events (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
provider TEXT NOT NULL CHECK(provider IN ('password', 'discord', 'magic_link', 'passkey')),
|
|
succeeded INTEGER NOT NULL CHECK(succeeded IN (0, 1)),
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
client_name TEXT,
|
|
operating_system TEXT,
|
|
device_type TEXT,
|
|
location_country TEXT,
|
|
location_region TEXT,
|
|
location_city TEXT,
|
|
failure_reason TEXT,
|
|
risk_flags TEXT,
|
|
credential_id TEXT,
|
|
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
session_ended_at TEXT,
|
|
session_end_reason TEXT,
|
|
sensitive_purged_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS user_emails (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
email TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
|
verified_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS magic_link_tokens (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
email TEXT NOT NULL COLLATE NOCASE,
|
|
purpose TEXT NOT NULL CHECK(purpose IN ('login', 'verify_email')),
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
expires_at TEXT NOT NULL,
|
|
consumed_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS magic_link_tokens_expiry_idx ON magic_link_tokens(expires_at);
|
|
CREATE TABLE IF NOT EXISTS passkeys (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
webauthn_user_id TEXT NOT NULL,
|
|
public_key BLOB NOT NULL,
|
|
counter INTEGER NOT NULL DEFAULT 0,
|
|
device_type TEXT NOT NULL,
|
|
backed_up INTEGER NOT NULL DEFAULT 0 CHECK(backed_up IN (0, 1)),
|
|
transports TEXT,
|
|
name TEXT NOT NULL,
|
|
last_used_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS passkeys_user_idx ON passkeys(user_id);
|
|
CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
flow TEXT NOT NULL CHECK(flow IN ('registration', 'authentication')),
|
|
challenge TEXT NOT NULL,
|
|
rp_id TEXT NOT NULL,
|
|
origin TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS webauthn_challenges_expiry_idx ON webauthn_challenges(expires_at);
|
|
CREATE TABLE IF NOT EXISTS deleted_accounts (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id),
|
|
original_username TEXT NOT NULL,
|
|
deleted_at TEXT NOT NULL,
|
|
deleted_by TEXT NOT NULL REFERENCES users(id),
|
|
reason TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS login_events_user_created_idx ON login_events(user_id, created_at DESC);
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-data-editor','Data Editor','Access to ISAC game-data management');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-user-operator','User Operator','View and moderate SHD.build users');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-access-manager','Access Manager','Manage user roles and direct permissions');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-admin','Admin','Full administrative access to all current and future ISAC capabilities');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-moderator','Community Oversight','Community oversight role reserved for build, guide, and comment tools');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-system-admin','system.admin','Unrestricted access to all administrative capabilities');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-isac-access','isac.access','Open the ISAC interface');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-data-manage','data.manage','Create and edit builder source data');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-data-view','data.view','View ISAC builder source data without changing live records');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-data-propose','data.propose','Suggest builder source-data additions and edits for approval');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-data-approve','data.approve','Review, approve, and reject proposed builder source-data changes');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-users-view','users.view','View users, activity, and content history');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-users-moderate','users.moderate','Block, ban, and reactivate users');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-users-roles','users.roles','Assign roles and direct permissions');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-users-delete','users.delete','Soft-delete user accounts while retaining authored content');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-users-identities','users.identities','View and manage linked external identities');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-audit-view','audit.view','View and search the immutable ISAC audit history');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-audit-revert','audit.revert','Attempt confirmed compensating reversals of supported ISAC audit events');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-metrics-view','metrics.view','View read-only application, runtime, and database health metrics');
|
|
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-avatars-review','avatars.review','Review and remove custom user avatars');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-data-manage');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-data-view');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-data-viewer','Data Viewer','Read-only access to ISAC game-data registries');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-data-contributor','Data Contributor','View game data and submit moderated change proposals');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-data-approver','Data Oversight','Review, correct, and decide proposals before applying approved changes to live game data');
|
|
UPDATE roles SET name='Community Oversight',description='Community oversight role reserved for build, guide, and comment tools' WHERE id='role-moderator';
|
|
UPDATE roles SET name='Data Oversight',description='Review, correct, and decide proposals before applying approved changes to live game data' WHERE id='role-data-approver';
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-viewer','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-viewer','permission-data-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-contributor','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-contributor','permission-data-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-contributor','permission-data-propose');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-approver','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-approver','permission-data-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-approver','permission-data-manage');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-approver','permission-data-approve');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-user-operator','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-user-operator','permission-users-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-user-operator','permission-users-moderate');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-user-operator','permission-avatars-review');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-access-manager','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-access-manager','permission-users-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-access-manager','permission-users-roles');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-access-manager','permission-users-identities');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-admin','permission-system-admin');
|
|
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-auditor','Auditor','Read-only access to ISAC audit history');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-auditor','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-auditor','permission-audit-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-auditor','permission-metrics-view');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-moderator','permission-isac-access');
|
|
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-moderator','permission-avatars-review');
|
|
CREATE TABLE IF NOT EXISTS oauth_states (state_hash TEXT PRIMARY KEY, provider TEXT NOT NULL, mode TEXT NOT NULL, user_id TEXT REFERENCES users(id) ON DELETE CASCADE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL);
|
|
CREATE TABLE IF NOT EXISTS data_change_proposals (
|
|
id TEXT PRIMARY KEY,
|
|
module TEXT NOT NULL CHECK(module IN ('attributes','talents','brands','gear','weapons','protocols','skillchips')),
|
|
action TEXT NOT NULL,
|
|
entity_id TEXT,
|
|
draft_entity_id TEXT,
|
|
applied_entity_id TEXT,
|
|
summary TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','failed')),
|
|
proposed_by TEXT NOT NULL REFERENCES users(id),
|
|
reviewed_by TEXT REFERENCES users(id),
|
|
review_note TEXT,
|
|
created_at TEXT NOT NULL,
|
|
reviewed_at TEXT,
|
|
applied_at TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS data_change_proposals_status_created_idx ON data_change_proposals(status,created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS data_change_proposals_module_status_idx ON data_change_proposals(module,status);
|
|
CREATE TABLE IF NOT EXISTS data_change_proposal_dependencies (
|
|
proposal_id TEXT NOT NULL REFERENCES data_change_proposals(id) ON DELETE CASCADE,
|
|
depends_on_proposal_id TEXT NOT NULL REFERENCES data_change_proposals(id) ON DELETE RESTRICT,
|
|
PRIMARY KEY(proposal_id,depends_on_proposal_id),
|
|
CHECK(proposal_id<>depends_on_proposal_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS data_change_proposal_dependencies_dependency_idx ON data_change_proposal_dependencies(depends_on_proposal_id);
|
|
CREATE TABLE IF NOT EXISTS contributor_agreements (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
guidelines_version TEXT NOT NULL,
|
|
accepted_at TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)),
|
|
enabled_at TEXT NOT NULL,
|
|
disabled_at TEXT
|
|
);
|
|
CREATE TABLE IF NOT EXISTS discord_profiles (
|
|
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
discord_id TEXT NOT NULL UNIQUE,
|
|
username TEXT NOT NULL,
|
|
display_name TEXT,
|
|
avatar_hash TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS users_username_nocase_idx ON users(username COLLATE NOCASE);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS users_login_username_nocase_idx ON users(login_username COLLATE NOCASE) WHERE login_username IS NOT NULL;
|
|
CREATE TABLE IF NOT EXISTS stat_attributes (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL CHECK(type IN ('Offensive', 'Defensive')),
|
|
category TEXT NOT NULL CHECK(category IN ('Weapon', 'Skill', 'Character')),
|
|
calculation_type TEXT NOT NULL CHECK(calculation_type IN ('Additive', 'Multiplicative')),
|
|
unit TEXT NOT NULL DEFAULT '',
|
|
gear_min REAL,
|
|
gear_max REAL,
|
|
weapon_min REAL,
|
|
weapon_max REAL,
|
|
skill_chip_min REAL,
|
|
skill_chip_max REAL,
|
|
cap_value REAL,
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
CHECK((gear_min IS NULL AND gear_max IS NULL) OR (gear_min IS NOT NULL AND gear_max IS NOT NULL AND gear_min <= gear_max)),
|
|
CHECK((weapon_min IS NULL AND weapon_max IS NULL) OR (weapon_min IS NOT NULL AND weapon_max IS NOT NULL AND weapon_min <= weapon_max)),
|
|
CHECK((skill_chip_min IS NULL AND skill_chip_max IS NULL) OR (skill_chip_min IS NOT NULL AND skill_chip_max IS NOT NULL AND skill_chip_min <= skill_chip_max)),
|
|
CHECK(cap_value IS NULL OR cap_value >= 0)
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS stat_attributes_name_nocase_idx ON stat_attributes(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS talents (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
category TEXT NOT NULL CHECK(category IN ('Weapon', 'Backpack', 'Body Armor', 'Vest')),
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS talents_name_category_nocase_idx ON talents(name COLLATE NOCASE, category);
|
|
CREATE TABLE IF NOT EXISTS talent_tiers (
|
|
id TEXT PRIMARY KEY,
|
|
talent_id TEXT NOT NULL REFERENCES talents(id) ON DELETE CASCADE,
|
|
tier TEXT NOT NULL CHECK(tier IN ('I', 'II')),
|
|
description TEXT NOT NULL,
|
|
trigger_text TEXT NOT NULL,
|
|
effect_text TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(talent_id, tier)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS talent_triggers (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
supports_uptime INTEGER NOT NULL DEFAULT 0 CHECK(supports_uptime IN (0, 1)),
|
|
min_uptime REAL,
|
|
max_uptime REAL,
|
|
calculation_script TEXT,
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
CHECK(
|
|
(supports_uptime = 0 AND min_uptime IS NULL AND max_uptime IS NULL)
|
|
OR
|
|
(supports_uptime = 1 AND min_uptime IS NOT NULL AND max_uptime IS NOT NULL
|
|
AND min_uptime >= 0 AND max_uptime <= 100 AND min_uptime <= max_uptime)
|
|
)
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS talent_triggers_name_nocase_idx ON talent_triggers(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS talent_tier_triggers (
|
|
talent_tier_id TEXT NOT NULL REFERENCES talent_tiers(id) ON DELETE CASCADE,
|
|
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
|
|
PRIMARY KEY(talent_tier_id, trigger_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS talent_tier_effects (
|
|
id TEXT PRIMARY KEY,
|
|
talent_tier_id TEXT NOT NULL REFERENCES talent_tiers(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
UNIQUE(talent_tier_id, stat_attribute_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS talent_description_templates (
|
|
talent_id TEXT PRIMARY KEY REFERENCES talents(id) ON DELETE CASCADE,
|
|
template TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS talent_weapon_scopes (
|
|
talent_id TEXT PRIMARY KEY REFERENCES talents(id) ON DELETE CASCADE,
|
|
scope TEXT NOT NULL CHECK(scope IN ('Standard', 'Exotic'))
|
|
);
|
|
INSERT OR IGNORE INTO talent_weapon_scopes(talent_id,scope)
|
|
SELECT id,'Standard' FROM talents WHERE category='Weapon';
|
|
CREATE TABLE IF NOT EXISTS weapon_talent_slots (
|
|
talent_id TEXT PRIMARY KEY REFERENCES talents(id) ON DELETE CASCADE,
|
|
slot INTEGER NOT NULL CHECK(slot IN (1, 2))
|
|
);
|
|
CREATE TABLE IF NOT EXISTS brand_sets (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS brand_sets_name_nocase_idx ON brand_sets(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS brand_set_effects (
|
|
id TEXT PRIMARY KEY,
|
|
brand_set_id TEXT NOT NULL REFERENCES brand_sets(id) ON DELETE CASCADE,
|
|
pieces_required INTEGER NOT NULL CHECK(pieces_required IN (2, 3, 4)),
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL CHECK(value >= 0),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(brand_set_id, pieces_required)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS gear (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
slot TEXT NOT NULL CHECK(slot IN ('Holster', 'Gloves', 'Backpack', 'Knee Pads', 'Body Armor', 'Mask')),
|
|
tier INTEGER NOT NULL CHECK(tier BETWEEN 0 AND 2),
|
|
level INTEGER NOT NULL CHECK(level BETWEEN 1 AND 50),
|
|
base_health_min REAL NOT NULL CHECK(base_health_min >= 0),
|
|
base_health_max REAL NOT NULL CHECK(base_health_max >= base_health_min),
|
|
base_armor_min REAL,
|
|
base_armor_max REAL,
|
|
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
CHECK(
|
|
(slot IN ('Gloves', 'Knee Pads', 'Body Armor') AND base_armor_min IS NOT NULL AND base_armor_max IS NOT NULL AND base_armor_min >= 0 AND base_armor_max >= base_armor_min)
|
|
OR
|
|
(slot NOT IN ('Gloves', 'Knee Pads', 'Body Armor') AND base_armor_min IS NULL AND base_armor_max IS NULL)
|
|
)
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS gear_name_nocase_idx ON gear(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS gear_brand_sets (
|
|
gear_id TEXT NOT NULL REFERENCES gear(id) ON DELETE CASCADE,
|
|
brand_set_id TEXT NOT NULL REFERENCES brand_sets(id),
|
|
PRIMARY KEY(gear_id, brand_set_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS gear_talents (
|
|
gear_id TEXT NOT NULL REFERENCES gear(id) ON DELETE CASCADE,
|
|
talent_id TEXT NOT NULL REFERENCES talents(id),
|
|
PRIMARY KEY(gear_id, talent_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS gear_tactical_augments (
|
|
id TEXT PRIMARY KEY,
|
|
gear_slot TEXT NOT NULL CHECK(gear_slot IN ('Holster', 'Gloves', 'Backpack', 'Knee Pads', 'Body Armor', 'Mask')),
|
|
augment_slot INTEGER NOT NULL CHECK(augment_slot BETWEEN 1 AND 4),
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL,
|
|
unlock_cost INTEGER NOT NULL CHECK(unlock_cost >= 0),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(gear_slot, augment_slot),
|
|
CHECK(
|
|
(gear_slot IN ('Backpack', 'Body Armor') AND augment_slot BETWEEN 1 AND 4)
|
|
OR
|
|
(gear_slot NOT IN ('Backpack', 'Body Armor') AND augment_slot BETWEEN 1 AND 3)
|
|
)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS weapons (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
category TEXT NOT NULL CHECK(category IN ('Shotgun', 'Rifle', 'MMR', 'AR', 'LMG', 'SMG')),
|
|
is_exotic INTEGER NOT NULL DEFAULT 0 CHECK(is_exotic IN (0, 1)),
|
|
native_tier INTEGER NOT NULL CHECK(native_tier BETWEEN 0 AND 2),
|
|
damage_type TEXT NOT NULL CHECK(damage_type IN ('Blast', 'Pierce', 'Shredding')),
|
|
rpm REAL NOT NULL CHECK(rpm >= 0),
|
|
magazine REAL NOT NULL CHECK(magazine >= 0),
|
|
reload_speed REAL NOT NULL CHECK(reload_speed >= 0),
|
|
accuracy REAL NOT NULL CHECK(accuracy >= 0),
|
|
stability REAL NOT NULL CHECK(stability >= 0),
|
|
exotic_talent_id TEXT REFERENCES talents(id),
|
|
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
CHECK(
|
|
(is_exotic = 1 AND exotic_talent_id IS NOT NULL)
|
|
OR
|
|
(is_exotic = 0 AND exotic_talent_id IS NULL)
|
|
)
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS weapons_name_nocase_idx ON weapons(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS weapon_tier_ranges (
|
|
weapon_id TEXT NOT NULL REFERENCES weapons(id) ON DELETE CASCADE,
|
|
tier INTEGER NOT NULL CHECK(tier BETWEEN 0 AND 2),
|
|
damage_min REAL NOT NULL CHECK(damage_min >= 0),
|
|
damage_max REAL NOT NULL CHECK(damage_max >= damage_min),
|
|
firepower_min REAL NOT NULL CHECK(firepower_min >= 0),
|
|
firepower_max REAL NOT NULL CHECK(firepower_max >= firepower_min),
|
|
toughness_min REAL NOT NULL CHECK(toughness_min >= 0),
|
|
toughness_max REAL NOT NULL CHECK(toughness_max >= toughness_min),
|
|
engineering_min REAL NOT NULL CHECK(engineering_min >= 0),
|
|
engineering_max REAL NOT NULL CHECK(engineering_max >= engineering_min),
|
|
PRIMARY KEY(weapon_id, tier)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS weapon_talent_options (
|
|
weapon_id TEXT NOT NULL REFERENCES weapons(id) ON DELETE CASCADE,
|
|
talent_slot INTEGER NOT NULL CHECK(talent_slot IN (1, 2)),
|
|
talent_id TEXT NOT NULL REFERENCES talents(id),
|
|
PRIMARY KEY(weapon_id, talent_slot, talent_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS weapon_augmentation_effects (
|
|
id TEXT PRIMARY KEY,
|
|
weapon_id TEXT NOT NULL REFERENCES weapons(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL CHECK(value > 0),
|
|
sort_order INTEGER NOT NULL CHECK(sort_order IN (1, 2)),
|
|
UNIQUE(weapon_id, sort_order),
|
|
UNIQUE(weapon_id, stat_attribute_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS weapon_base_attribute_bonuses (
|
|
id TEXT PRIMARY KEY,
|
|
weapon_id TEXT NOT NULL REFERENCES weapons(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL CHECK(value > 0),
|
|
sort_order INTEGER NOT NULL CHECK(sort_order >= 1),
|
|
UNIQUE(weapon_id, stat_attribute_id),
|
|
UNIQUE(weapon_id, sort_order)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS os_protocols (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
quality TEXT NOT NULL CHECK(quality IN ('Standard', 'Specialized', 'Superior', 'High-end')),
|
|
primary_attribute_type TEXT NOT NULL CHECK(primary_attribute_type IN ('Firepower', 'Toughness', 'Engineering')),
|
|
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS os_protocols_name_nocase_idx ON os_protocols(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_primary_effects (
|
|
id TEXT PRIMARY KEY,
|
|
protocol_id TEXT NOT NULL UNIQUE REFERENCES os_protocols(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
base_value REAL NOT NULL CHECK(base_value >= 0)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_secondary_profiles (
|
|
id TEXT PRIMARY KEY,
|
|
quality TEXT NOT NULL CHECK(quality IN ('Standard', 'Specialized', 'Superior', 'High-end')),
|
|
primary_attribute_type TEXT NOT NULL CHECK(primary_attribute_type IN ('Firepower', 'Toughness', 'Engineering')),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(quality, primary_attribute_type)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_secondary_effects (
|
|
id TEXT PRIMARY KEY,
|
|
profile_id TEXT NOT NULL REFERENCES os_protocol_secondary_profiles(id) ON DELETE CASCADE,
|
|
effect_role TEXT NOT NULL CHECK(effect_role IN ('Core', 'Damage', 'Health')),
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
value REAL NOT NULL CHECK(value >= 0),
|
|
UNIQUE(profile_id, effect_role),
|
|
UNIQUE(profile_id, stat_attribute_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_talents (
|
|
id TEXT PRIMARY KEY,
|
|
protocol_id TEXT NOT NULL UNIQUE REFERENCES os_protocols(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description_template TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS os_protocol_talents_name_nocase_idx ON os_protocol_talents(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_talent_triggers (
|
|
talent_id TEXT NOT NULL REFERENCES os_protocol_talents(id) ON DELETE CASCADE,
|
|
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
|
|
PRIMARY KEY(talent_id, trigger_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS os_protocol_talent_effects (
|
|
id TEXT PRIMARY KEY,
|
|
talent_id TEXT NOT NULL REFERENCES os_protocol_talents(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
base_value REAL NOT NULL,
|
|
sort_order INTEGER NOT NULL CHECK(sort_order BETWEEN 1 AND 12),
|
|
UNIQUE(talent_id, stat_attribute_id),
|
|
UNIQUE(talent_id, sort_order)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_sets (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0, 1)),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS skill_chip_sets_name_nocase_idx ON skill_chip_sets(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_two_piece_bonuses (
|
|
id TEXT PRIMARY KEY,
|
|
skill_chip_set_id TEXT NOT NULL UNIQUE REFERENCES skill_chip_sets(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
min_value REAL NOT NULL,
|
|
max_value REAL NOT NULL,
|
|
CHECK(max_value >= min_value)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_primary_options (
|
|
skill_chip_set_id TEXT NOT NULL REFERENCES skill_chip_sets(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
PRIMARY KEY(skill_chip_set_id, stat_attribute_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_secondary_options (
|
|
skill_chip_set_id TEXT NOT NULL REFERENCES skill_chip_sets(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
PRIMARY KEY(skill_chip_set_id, stat_attribute_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_talents (
|
|
id TEXT PRIMARY KEY,
|
|
skill_chip_set_id TEXT NOT NULL UNIQUE REFERENCES skill_chip_sets(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description_template TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS skill_chip_talents_name_nocase_idx ON skill_chip_talents(name COLLATE NOCASE);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_talent_triggers (
|
|
talent_id TEXT NOT NULL REFERENCES skill_chip_talents(id) ON DELETE CASCADE,
|
|
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
|
|
PRIMARY KEY(talent_id, trigger_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS skill_chip_talent_effects (
|
|
id TEXT PRIMARY KEY,
|
|
talent_id TEXT NOT NULL REFERENCES skill_chip_talents(id) ON DELETE CASCADE,
|
|
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
|
min_value REAL NOT NULL,
|
|
max_value REAL NOT NULL,
|
|
sort_order INTEGER NOT NULL CHECK(sort_order BETWEEN 1 AND 12),
|
|
CHECK(max_value >= min_value),
|
|
UNIQUE(talent_id, stat_attribute_id),
|
|
UNIQUE(talent_id, sort_order)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS game_data_icons (
|
|
entity_type TEXT NOT NULL CHECK(entity_type IN ('talent', 'brand_set', 'os_protocol', 'skill_chip')),
|
|
entity_id TEXT NOT NULL,
|
|
storage_key TEXT NOT NULL UNIQUE,
|
|
byte_size INTEGER NOT NULL CHECK(byte_size > 0),
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY(entity_type, entity_id)
|
|
);
|