Skip to main content

Database Schema Overview

GAC uses a relational database schema with 9 core tables that work together to provide granular access control. The schema is designed for flexibility and can be customized to fit your application’s needs.

Core Tables

Stores user authentication and session data.
CREATE TABLE `gac_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `person_id` int NOT NULL,
  `username` varchar(60) NOT NULL,
  `password` varchar(255) NOT NULL,
  `failed_attempt_count` tinyint(1) NOT NULL DEFAULT '0',
  `failed_attempt_date` bigint DEFAULT NULL,
  `last_login` bigint DEFAULT NULL,
  `last_login_ip` varchar(39) DEFAULT NULL,
  `last_login_type` enum('0','1') DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  KEY `person_id` (`person_id`),
  CONSTRAINT `gac_user_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `glb_person` (`id`)
);
Key Fields:
  • person_id: Links to person directory (customizable)
  • failed_attempt_count: Security feature for brute force protection
  • last_login_type: ‘0’ = Manual, ‘1’ = Google OAuth
Manages API clients and external system access.
CREATE TABLE `gac_client` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(60) NOT NULL,
  `description` text,
  `client_id` varchar(255) NOT NULL,
  `client_secret` varchar(255) NOT NULL COMMENT 'Must be hashed',
  `failed_attempt_count` tinyint(1) NOT NULL DEFAULT '0',
  `failed_attempt_date` bigint DEFAULT NULL,
  `last_login` bigint DEFAULT NULL,
  `last_login_ip` varchar(39) DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `client_id` (`client_id`)
);
Always hash client_secret before storage. Never store plain text secrets.
Defines roles that can be assigned to users and clients.
CREATE TABLE `gac_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `code` varchar(30) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `code` (`code`)
);
Example roles:
  • system_administrator: Full system access
  • system_supervisor: Read-only system access
Links roles to users or clients with priority levels.
CREATE TABLE `gac_role_entity` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role_id` int NOT NULL,
  `entity_type` enum('1','2') NOT NULL COMMENT '1=User, 2=Client',
  `entity_id` int NOT NULL,
  `priority` enum('0','1','2','3','4') NOT NULL DEFAULT '0',
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `role_unique` (`role_id`,`entity_type`,`entity_id`),
  UNIQUE KEY `priority_unique` (`entity_type`,`entity_id`,`priority`),
  CONSTRAINT `gac_role_entity_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `gac_role` (`id`)
);
Priority System:
  • 0: Primary role (each entity can have only one)
  • 1-4: Secondary roles (one per priority level)
Organizes modules into logical categories.
CREATE TABLE `gac_module_category` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(60) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);
Default categories:
  • Sistema: System administration modules
  • Usuario: User profile modules
  • Directorios: Directory management modules
Defines individual modules/features in your application.
CREATE TABLE `gac_module` (
  `id` int NOT NULL AUTO_INCREMENT,
  `module_category_id` int NOT NULL,
  `name` varchar(60) NOT NULL,
  `code` varchar(40) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `base_route` varchar(255) NOT NULL,
  `is_developing` enum('0','1') NOT NULL DEFAULT '1',
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `code` (`code`),
  KEY `module_category_id` (`module_category_id`),
  CONSTRAINT `gac_module_ibfk_1` FOREIGN KEY (`module_category_id`) REFERENCES `gac_module_category` (`id`)
);
Important Fields:
  • code: Used in permission checks (e.g., users, my_profile)
  • base_route: URL path (supports parameters like /users/{:user_id}/access)
  • is_developing: When ‘1’, only users with ‘dev’ feature can access
The core permission table that grants access to modules.
CREATE TABLE `gac_module_access` (
  `id` int NOT NULL AUTO_INCREMENT,
  `from_entity_type` enum('0','1','2') NOT NULL COMMENT '0=Role, 1=User, 2=Client',
  `from_entity_id` int NOT NULL,
  `to_entity_type` enum('0','1') NOT NULL COMMENT '0=Category, 1=Module',
  `to_entity_id` int NOT NULL,
  `feature` set('0','1','2','3','4','5') NOT NULL,
  `level` enum('0','1','2') NOT NULL DEFAULT '1',
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `access_unique` (`from_entity_type`,`from_entity_id`,`to_entity_type`,`to_entity_id`)
);
Feature Flags:
  • 0: Create
  • 1: Read
  • 2: Update
  • 3: Delete
  • 4: Trash (works with read, update, delete)
  • 5: Development mode access
Access Levels:
  • 0: Low (restricted access)
  • 1: Normal (standard access)
  • 2: High (full access)
Defines types of restrictions (e.g., by branch, by date).
CREATE TABLE `gac_restriction_category` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(60) NOT NULL,
  `code` varchar(30) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `code` (`code`)
);
Default categories:
  • by_branch: Entity-based restrictions
  • by_date: Time-based restrictions
Specific restriction methods within each category.
CREATE TABLE `gac_restriction_method` (
  `id` int NOT NULL AUTO_INCREMENT,
  `restriction_category_id` int NOT NULL,
  `name` varchar(60) NOT NULL,
  `code` varchar(30) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `code_unique` (`restriction_category_id`,`code`),
  CONSTRAINT `gac_restriction_method_ibfk_1` FOREIGN KEY (`restriction_category_id`) REFERENCES `gac_restriction_category` (`id`)
);
Branch Methods:
  • deny: All except specified entities
  • allow: Only specified entities
Date Methods:
  • in_range: Within date range
  • out_range: Outside date range
  • before: Before specific date
  • after: After specific date
Actual restrictions applied to entities.
CREATE TABLE `gac_restriction` (
  `id` int NOT NULL AUTO_INCREMENT,
  `entity_type` enum('0','1','2','3') NOT NULL COMMENT '0=Role, 1=User, 2=Client, 3=All',
  `entity_id` int NOT NULL,
  `restriction_method_id` int NOT NULL,
  `data` text NOT NULL COMMENT 'JSON validation data',
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `restriction_unique` (`entity_type`,`entity_id`,`restriction_method_id`),
  CONSTRAINT `gac_restriction_ibfk_1` FOREIGN KEY (`restriction_method_id`) REFERENCES `gac_restriction_method` (`id`)
);
The data field stores JSON configuration specific to each restriction type.

Table Relationships

Installing the Schema

1

Import the SQL file

The complete schema is provided in tables.sql. Import it into your MySQL database:
mysql -u username -p database_name < tables.sql
2

Verify table creation

Check that all 10 tables were created successfully:
SHOW TABLES LIKE 'gac_%';
SHOW TABLES LIKE 'glb_%';
You should see:
  • gac_client
  • gac_module
  • gac_module_access
  • gac_module_category
  • gac_restriction
  • gac_restriction_category
  • gac_restriction_method
  • gac_role
  • gac_role_entity
  • gac_user
  • glb_person
3

Review sample data

The SQL file includes sample data for:
  • 3 module categories (System, User, Directories)
  • 14 modules (users, roles, my_profile, etc.)
  • 2 roles (system_administrator, system_supervisor)
  • 2 restriction categories with 6 methods
  • 3 module access grants for the administrator role

Customizing the Schema

Adapting the Person Table

The glb_person table is designed to be customized for your application:
CREATE TABLE `glb_person` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(50) NOT NULL,
  `last_name` varchar(50) NOT NULL,
  `email` varchar(120) NOT NULL,
  `email_verified_date` bigint DEFAULT NULL,
  `google_id` varchar(45) DEFAULT NULL,
  `google_link_date` bigint DEFAULT NULL,
  `sex` enum('0','1') DEFAULT NULL COMMENT '0=Male, 1=Female',
  `is_disabled` enum('0','1') NOT NULL DEFAULT '0',
  `created_at` bigint NOT NULL,
  `updated_at` bigint DEFAULT NULL,
  `deleted_at` bigint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  UNIQUE KEY `google_id` (`google_id`)
);
You can rename this table or add custom fields (phone, address, etc.) without affecting GAC functionality. Just maintain the foreign key relationship in gac_user.person_id.

Adding Custom Fields

You can extend any GAC table with custom fields:
-- Add custom fields to gac_user
ALTER TABLE gac_user 
  ADD COLUMN department_id INT DEFAULT NULL,
  ADD COLUMN employee_code VARCHAR(20) DEFAULT NULL;

-- Add custom fields to gac_module
ALTER TABLE gac_module 
  ADD COLUMN icon VARCHAR(50) DEFAULT NULL,
  ADD COLUMN sort_order INT DEFAULT 0;

-- Add custom fields to gac_role
ALTER TABLE gac_role 
  ADD COLUMN color VARCHAR(7) DEFAULT NULL,
  ADD COLUMN is_system_role ENUM('0','1') DEFAULT '0';

Using Different Table Prefixes

If you want to use different prefixes, update the table names and foreign key constraints:
-- Example: Changing from 'gac_' to 'acl_'
RENAME TABLE gac_user TO acl_user;
RENAME TABLE gac_role TO acl_role;
-- ... rename all tables

-- Update foreign key references
ALTER TABLE acl_role_entity 
  DROP FOREIGN KEY gac_role_entity_ibfk_1;
  
ALTER TABLE acl_role_entity 
  ADD CONSTRAINT acl_role_entity_ibfk_1 
  FOREIGN KEY (role_id) REFERENCES acl_role(id);
If you change table names, you’ll need to modify the DatabaseAdapter class to use your custom table names. See Custom Adapters for details.

Soft Deletes

All GAC tables support soft deletes via the deleted_at field:
-- Soft delete a user
UPDATE gac_user 
SET deleted_at = UNIX_TIMESTAMP() 
WHERE id = 1;

-- Soft deleted records are automatically excluded from queries
SELECT * FROM gac_user WHERE deleted_at IS NULL;

-- Restore a soft deleted record
UPDATE gac_user 
SET deleted_at = NULL 
WHERE id = 1;

Timestamp Format

GAC uses Unix timestamps (bigint) for all date/time fields:
-- Current timestamp
INSERT INTO gac_user (..., created_at) VALUES (..., UNIX_TIMESTAMP());

-- Converting to readable format
SELECT 
  username,
  FROM_UNIXTIME(created_at) as created_date,
  FROM_UNIXTIME(last_login) as last_login_date
FROM gac_user;

Performance Optimization

The schema includes essential indexes. For large datasets, consider adding:
-- Index for frequent permission lookups
CREATE INDEX idx_access_lookup 
ON gac_module_access(from_entity_type, from_entity_id, is_disabled, deleted_at);

-- Index for restriction queries
CREATE INDEX idx_restriction_lookup 
ON gac_restriction(entity_type, entity_id, is_disabled, deleted_at);

-- Index for role queries
CREATE INDEX idx_role_entity_lookup 
ON gac_role_entity(entity_type, entity_id, is_disabled, deleted_at);

Query Optimization

-- Use covering indexes for frequently accessed columns
CREATE INDEX idx_module_access_features 
ON gac_module_access(to_entity_id, feature, level) 
WHERE deleted_at IS NULL AND is_disabled = '0';

-- Partition large tables by year (if you have millions of records)
ALTER TABLE gac_user 
PARTITION BY RANGE (created_at) (
  PARTITION p2024 VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01')),
  PARTITION p2025 VALUES LESS THAN (UNIX_TIMESTAMP('2026-01-01')),
  PARTITION p_future VALUES LESS THAN MAXVALUE
);

Next Steps

Setup Adapters

Configure database and cache adapters

Checking Permissions

Learn how to check permissions in your code

Build docs developers (and LLMs) love