yiekheng a37b36196d feat(db): add username + password_hash to operators
Migration 0010 widens the existing operators table for username +
password auth. Backfills 'admin' on the seed row so the NOT NULL
constraint succeeds; password_hash stays nullable so the operator is
forced to set one via scripts/set-password.sh before they can sign in.
Adds a unique index on lower(username).

seed.ts also picks up the new username field (defaults to 'admin' so
re-running scripts/db.sh seed stays idempotent against the backfilled row).
2026-05-10 17:35:01 +08:00

1071 lines
27 KiB
JSON

{
"id": "7c8abe6f-1d2f-48e0-898d-019bf7aa2b0e",
"prevId": "0399e112-d96c-4dda-a3ac-a94d09dd0d6d",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.audit_log": {
"name": "audit_log",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"operator_id": {
"name": "operator_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": true
},
"action": {
"name": "action",
"type": "text",
"primaryKey": false,
"notNull": true
},
"target_type": {
"name": "target_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"target_id": {
"name": "target_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"payload": {
"name": "payload",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"audit_log_operator_id_operators_id_fk": {
"name": "audit_log_operator_id_operators_id_fk",
"tableFrom": "audit_log",
"tableTo": "operators",
"columnsFrom": [
"operator_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.auth_sessions": {
"name": "auth_sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"operator_id": {
"name": "operator_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"last_used_at": {
"name": "last_used_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"ip_address": {
"name": "ip_address",
"type": "inet",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"auth_sessions_operator_id_operators_id_fk": {
"name": "auth_sessions_operator_id_operators_id_fk",
"tableFrom": "auth_sessions",
"tableTo": "operators",
"columnsFrom": [
"operator_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"auth_sessions_token_hash_unique": {
"name": "auth_sessions_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.cache_entries": {
"name": "cache_entries",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"value": {
"name": "value",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.media_files": {
"name": "media_files",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"operator_id": {
"name": "operator_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"filename_original": {
"name": "filename_original",
"type": "text",
"primaryKey": false,
"notNull": true
},
"mime_type": {
"name": "mime_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"size_bytes": {
"name": "size_bytes",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"sha256": {
"name": "sha256",
"type": "text",
"primaryKey": false,
"notNull": true
},
"storage_path": {
"name": "storage_path",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"media_files_operator_id_operators_id_fk": {
"name": "media_files_operator_id_operators_id_fk",
"tableFrom": "media_files",
"tableTo": "operators",
"columnsFrom": [
"operator_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.operators": {
"name": "operators",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"telegram_user_id": {
"name": "telegram_user_id",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'admin'"
},
"default_timezone": {
"name": "default_timezone",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'Asia/Kuala_Lumpur'"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"operators_telegram_user_id_uq": {
"name": "operators_telegram_user_id_uq",
"columns": [
{
"expression": "telegram_user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"operators_username_uq": {
"name": "operators_username_uq",
"columns": [
{
"expression": "lower(\"username\")",
"asc": true,
"isExpression": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.rate_limit_buckets": {
"name": "rate_limit_buckets",
"schema": "",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true
},
"window_start": {
"name": "window_start",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"count": {
"name": "count",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.reminder_messages": {
"name": "reminder_messages",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"reminder_id": {
"name": "reminder_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"position": {
"name": "position",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"kind": {
"name": "kind",
"type": "text",
"primaryKey": false,
"notNull": true
},
"text_content": {
"name": "text_content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"media_id": {
"name": "media_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"reminder_messages_reminder_id_reminders_id_fk": {
"name": "reminder_messages_reminder_id_reminders_id_fk",
"tableFrom": "reminder_messages",
"tableTo": "reminders",
"columnsFrom": [
"reminder_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"reminder_messages_media_id_media_files_id_fk": {
"name": "reminder_messages_media_id_media_files_id_fk",
"tableFrom": "reminder_messages",
"tableTo": "media_files",
"columnsFrom": [
"media_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.reminder_run_targets": {
"name": "reminder_run_targets",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"group_id": {
"name": "group_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"group_label": {
"name": "group_label",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true
},
"wa_message_id": {
"name": "wa_message_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"latency_ms": {
"name": "latency_ms",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"reminder_run_targets_run_id_reminder_runs_id_fk": {
"name": "reminder_run_targets_run_id_reminder_runs_id_fk",
"tableFrom": "reminder_run_targets",
"tableTo": "reminder_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"reminder_run_targets_group_id_whatsapp_groups_id_fk": {
"name": "reminder_run_targets_group_id_whatsapp_groups_id_fk",
"tableFrom": "reminder_run_targets",
"tableTo": "whatsapp_groups",
"columnsFrom": [
"group_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.reminder_runs": {
"name": "reminder_runs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"reminder_id": {
"name": "reminder_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"reminder_name": {
"name": "reminder_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"fired_at": {
"name": "fired_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true
},
"error_summary": {
"name": "error_summary",
"type": "text",
"primaryKey": false,
"notNull": false
},
"archived_at": {
"name": "archived_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"reminder_runs_reminder_id_reminders_id_fk": {
"name": "reminder_runs_reminder_id_reminders_id_fk",
"tableFrom": "reminder_runs",
"tableTo": "reminders",
"columnsFrom": [
"reminder_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.reminder_targets": {
"name": "reminder_targets",
"schema": "",
"columns": {
"reminder_id": {
"name": "reminder_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"group_id": {
"name": "group_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"position": {
"name": "position",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {
"reminder_targets_reminder_id_reminders_id_fk": {
"name": "reminder_targets_reminder_id_reminders_id_fk",
"tableFrom": "reminder_targets",
"tableTo": "reminders",
"columnsFrom": [
"reminder_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"reminder_targets_group_id_whatsapp_groups_id_fk": {
"name": "reminder_targets_group_id_whatsapp_groups_id_fk",
"tableFrom": "reminder_targets",
"tableTo": "whatsapp_groups",
"columnsFrom": [
"group_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"reminder_targets_reminder_id_group_id_pk": {
"name": "reminder_targets_reminder_id_group_id_pk",
"columns": [
"reminder_id",
"group_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.reminders": {
"name": "reminders",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"account_id": {
"name": "account_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"schedule_kind": {
"name": "schedule_kind",
"type": "text",
"primaryKey": false,
"notNull": true
},
"scheduled_at": {
"name": "scheduled_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"rrule": {
"name": "rrule",
"type": "text",
"primaryKey": false,
"notNull": false
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"ends_at": {
"name": "ends_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"max_runs": {
"name": "max_runs",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'active'"
},
"created_by": {
"name": "created_by",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"last_fired_at": {
"name": "last_fired_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"delivery_window_start_hour": {
"name": "delivery_window_start_hour",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 6
},
"delivery_window_end_hour": {
"name": "delivery_window_end_hour",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 18
}
},
"indexes": {},
"foreignKeys": {
"reminders_account_id_whatsapp_accounts_id_fk": {
"name": "reminders_account_id_whatsapp_accounts_id_fk",
"tableFrom": "reminders",
"tableTo": "whatsapp_accounts",
"columnsFrom": [
"account_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"reminders_created_by_operators_id_fk": {
"name": "reminders_created_by_operators_id_fk",
"tableFrom": "reminders",
"tableTo": "operators",
"columnsFrom": [
"created_by"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.whatsapp_accounts": {
"name": "whatsapp_accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"operator_id": {
"name": "operator_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"label": {
"name": "label",
"type": "text",
"primaryKey": false,
"notNull": true
},
"phone_number": {
"name": "phone_number",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"last_connected_at": {
"name": "last_connected_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"last_qr_at": {
"name": "last_qr_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"last_qr_png": {
"name": "last_qr_png",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"whatsapp_accounts_operator_label_uq": {
"name": "whatsapp_accounts_operator_label_uq",
"columns": [
{
"expression": "operator_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "label",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"whatsapp_accounts_operator_id_operators_id_fk": {
"name": "whatsapp_accounts_operator_id_operators_id_fk",
"tableFrom": "whatsapp_accounts",
"tableTo": "operators",
"columnsFrom": [
"operator_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.whatsapp_groups": {
"name": "whatsapp_groups",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"account_id": {
"name": "account_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"wa_group_jid": {
"name": "wa_group_jid",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"participant_count": {
"name": "participant_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"is_archived": {
"name": "is_archived",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"last_synced_at": {
"name": "last_synced_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"whatsapp_groups_account_jid_uq": {
"name": "whatsapp_groups_account_jid_uq",
"columns": [
{
"expression": "account_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "wa_group_jid",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"whatsapp_groups_account_id_whatsapp_accounts_id_fk": {
"name": "whatsapp_groups_account_id_whatsapp_accounts_id_fk",
"tableFrom": "whatsapp_groups",
"tableTo": "whatsapp_accounts",
"columnsFrom": [
"account_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}