Skip to main content

Database Schema

Core database tables and their relationships in AsaHome Cloud.

Entity Relationship Diagram


Core Tables

users

Stores user accounts with authentication and customer data synced from Laravel.

ColumnTypeNullableDefaultDescription
idUUIDNouuid_generate_v4()Primary key
emailVARCHAR(255)No-Unique email address
passwordVARCHAR(255)No-bcrypt hashed password
firstNameVARCHAR(100)No-User's first name
lastNameVARCHAR(100)No-User's last name
roleVARCHAR(20)No'user'user or admin
sync_idBIGINTYes-Laravel customer ID (for sync)
mrrDECIMAL(10,2)Yes-Monthly recurring revenue
isActiveBOOLEANNotrueAccount status
isEmailVerifiedBOOLEANNofalseEmail verification status
createdAtTIMESTAMPNonow()Creation timestamp
updatedAtTIMESTAMPNonow()Last update timestamp

Indexes

IndexColumnsType
PK_usersidPrimary Key
UQ_users_emailemailUnique
UQ_users_sync_idsync_idUnique (nullable)
IDX_users_roleroleB-tree
IDX_users_isActiveisActiveB-tree

Example

SELECT id, email, firstName, lastName, role, isActive
FROM users
WHERE email = 'user@example.com';
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"isActive": true
}

devices

Registered AsaHome OS devices.

ColumnTypeNullableDefaultDescription
idUUIDNouuid_generate_v4()Primary key
uuidUUIDNo-Device UUID (from AsaHome OS)
deviceIdVARCHAR(255)No-Human-readable device ID
nameVARCHAR(255)No-Device display name
ipAddressVARCHAR(45)Yes-Last known IP address
portVARCHAR(10)Yes'8123'Device API port
accessTokenTEXTYes-Encrypted device access token
isOnlineBOOLEANNofalseCurrent connection status
lastSeenAtTIMESTAMPYes-Last heartbeat time
metadataJSONBYes-Additional device info
createdAtTIMESTAMPNonow()Registration timestamp
updatedAtTIMESTAMPNonow()Last update timestamp

Indexes

IndexColumnsType
PK_devicesidPrimary Key
UQ_devices_uuiduuidUnique
UQ_devices_deviceIddeviceIdUnique
IDX_devices_isOnlineisOnlineB-tree

Metadata Schema

The metadata JSONB column typically contains:

{
"version": "2024.1.0",
"location": "Living Room",
"model": "RPi4",
"uptime": 86400,
"entities": 150,
"lastBackup": "2024-01-15T00:00:00Z"
}

device_users

Many-to-many relationship between users and devices with role-based access.

ColumnTypeNullableDefaultDescription
idUUIDNouuid_generate_v4()Primary key
userIdUUIDNo-Foreign key to users
deviceIdUUIDNo-Foreign key to devices
roleVARCHAR(20)No'viewer'owner, editor, or viewer
isActiveBOOLEANNotrueLink status
createdAtTIMESTAMPNonow()Creation timestamp
updatedAtTIMESTAMPNonow()Last update timestamp

Indexes

IndexColumnsType
PK_device_usersidPrimary Key
UQ_device_usersuserId, deviceIdUnique
IDX_device_users_userIduserIdB-tree
IDX_device_users_deviceIddeviceIdB-tree

Role Permissions

RoleViewControlSettingsDeleteShare
ownerYesYesYesYesYes
editorYesYesNoNoNo
viewerYesNoNoNoNo

refresh_tokens

Stores hashed refresh tokens for session management.

ColumnTypeNullableDefaultDescription
idUUIDNouuid_generate_v4()Primary key
userIdUUIDNo-Foreign key to users
tokenVARCHAR(255)No-SHA-256 hashed token
deviceIdVARCHAR(255)Yes-Client device identifier
expiresAtTIMESTAMPNo-Token expiration
isRevokedBOOLEANNofalseRevocation status
createdAtTIMESTAMPNonow()Creation timestamp

Indexes

IndexColumnsType
PK_refresh_tokensidPrimary Key
IDX_refresh_tokens_tokentokenB-tree
IDX_refresh_tokens_userIduserIdB-tree
IDX_refresh_tokens_expiresAtexpiresAtB-tree

Token Security

Tokens are stored as SHA-256 hashes:

import * as crypto from 'crypto';

const hashedToken = crypto
.createHash('sha256')
.update(refreshToken)
.digest('hex');

Additional Tables

audit_logs

Audit trail for security-relevant actions.

ColumnTypeDescription
idUUIDPrimary key
userIdUUIDUser who performed action
actionVARCHARAction type (login, logout, etc.)
resourceVARCHARAffected resource type
resourceIdUUIDAffected resource ID
ipAddressVARCHARClient IP address
userAgentTEXTClient user agent
metadataJSONBAdditional context
createdAtTIMESTAMPAction timestamp

login_attempts

Track login attempts for security monitoring.

ColumnTypeDescription
idUUIDPrimary key
emailVARCHARAttempted email
ipAddressVARCHARClient IP
successBOOLEANAttempt result
failReasonVARCHARFailure reason if failed
createdAtTIMESTAMPAttempt timestamp

tunnel_sessions

Active WebSocket tunnel sessions.

ColumnTypeDescription
idUUIDPrimary key
typeVARCHARuser or device
entityIdUUIDUser ID or Device ID
socketIdVARCHARSocket.IO socket ID
connectedAtTIMESTAMPConnection timestamp
lastActivityAtTIMESTAMPLast message timestamp
metadataJSONBConnection metadata

Common Queries

Get user's devices with roles

SELECT 
d.id,
d.uuid,
d.name,
d.isOnline,
d.lastSeenAt,
du.role
FROM devices d
INNER JOIN device_users du ON d.id = du.deviceId
WHERE du.userId = '550e8400-e29b-41d4-a716-446655440000'
AND du.isActive = true
ORDER BY d.name;

Get device with all users

SELECT 
d.*,
json_agg(json_build_object(
'userId', u.id,
'email', u.email,
'firstName', u.firstName,
'lastName', u.lastName,
'role', du.role
)) as users
FROM devices d
INNER JOIN device_users du ON d.id = du.deviceId
INNER JOIN users u ON du.userId = u.id
WHERE d.uuid = '550e8400-e29b-41d4-a716-446655440000'
AND du.isActive = true
GROUP BY d.id;

Revoke all tokens for user

UPDATE refresh_tokens
SET isRevoked = true
WHERE userId = '550e8400-e29b-41d4-a716-446655440000'
AND isRevoked = false;

Clean up expired tokens

DELETE FROM refresh_tokens
WHERE expiresAt < NOW() - INTERVAL '7 days';

Find users synced from Laravel

SELECT id, email, firstName, lastName, sync_id, mrr
FROM users
WHERE sync_id IS NOT NULL
ORDER BY sync_id;

TypeORM Entities

User Entity

@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
email: string;

@Column()
password: string;

@Column()
firstName: string;

@Column()
lastName: string;

@Column({ default: 'user' })
role: string;

@Column({ type: 'bigint', nullable: true, unique: true })
sync_id: number;

@Column({ type: 'decimal', precision: 10, scale: 2, nullable: true })
mrr: number;

@Column({ default: true })
isActive: boolean;

@OneToMany(() => DeviceUser, deviceUser => deviceUser.user)
deviceUsers: DeviceUser[];

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}

Device Entity

@Entity('devices')
export class Device {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ type: 'uuid', unique: true })
uuid: string;

@Column({ unique: true })
deviceId: string;

@Column()
name: string;

@Column({ nullable: true })
ipAddress: string;

@Column({ default: '8123' })
port: string;

@Column({ default: false })
isOnline: boolean;

@Column({ type: 'timestamp', nullable: true })
lastSeenAt: Date;

@Column({ type: 'jsonb', nullable: true })
metadata: Record<string, any>;

@OneToMany(() => DeviceUser, deviceUser => deviceUser.device)
deviceUsers: DeviceUser[];

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}