refactor: remove redundant role field, keep only is_admin

- Remove role column from users schema (migration 0003)
- Update auth and admin services to use is_admin only
- Remove role from JWT token payloads
- Update admin CLI to use is_admin field
- Update frontend admin page to use isAdmin boolean
- Fix security: remove console.log dumping credentials in settings

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-21 11:41:41 +01:00
parent fc44fef91a
commit 8f8abfc651
11 changed files with 789 additions and 62 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE `users` DROP COLUMN `role`;

View File

@@ -0,0 +1,748 @@
{
"version": "6",
"dialect": "sqlite",
"id": "071c98fb-6721-4da7-98cb-c16cb6aaf0c1",
"prevId": "542bddc5-2e08-49af-91b5-013a6c9584df",
"tables": {
"admin_actions": {
"name": "admin_actions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"admin_id": {
"name": "admin_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"action_type": {
"name": "action_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"target_user_id": {
"name": "target_user_id",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"details": {
"name": "details",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"admin_actions_admin_id_users_id_fk": {
"name": "admin_actions_admin_id_users_id_fk",
"tableFrom": "admin_actions",
"tableTo": "users",
"columnsFrom": [
"admin_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"admin_actions_target_user_id_users_id_fk": {
"name": "admin_actions_target_user_id_users_id_fk",
"tableFrom": "admin_actions",
"tableTo": "users",
"columnsFrom": [
"target_user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"award_progress": {
"name": "award_progress",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"award_id": {
"name": "award_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"worked_count": {
"name": "worked_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"confirmed_count": {
"name": "confirmed_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"total_required": {
"name": "total_required",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"worked_entities": {
"name": "worked_entities",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"confirmed_entities": {
"name": "confirmed_entities",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_calculated_at": {
"name": "last_calculated_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_qso_sync_at": {
"name": "last_qso_sync_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"award_progress_user_id_users_id_fk": {
"name": "award_progress_user_id_users_id_fk",
"tableFrom": "award_progress",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"award_progress_award_id_awards_id_fk": {
"name": "award_progress_award_id_awards_id_fk",
"tableFrom": "award_progress",
"tableTo": "awards",
"columnsFrom": [
"award_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"awards": {
"name": "awards",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"definition": {
"name": "definition",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"is_active": {
"name": "is_active",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"qso_changes": {
"name": "qso_changes",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"job_id": {
"name": "job_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"qso_id": {
"name": "qso_id",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"change_type": {
"name": "change_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"before_data": {
"name": "before_data",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"after_data": {
"name": "after_data",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"qso_changes_job_id_sync_jobs_id_fk": {
"name": "qso_changes_job_id_sync_jobs_id_fk",
"tableFrom": "qso_changes",
"tableTo": "sync_jobs",
"columnsFrom": [
"job_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"qso_changes_qso_id_qsos_id_fk": {
"name": "qso_changes_qso_id_qsos_id_fk",
"tableFrom": "qso_changes",
"tableTo": "qsos",
"columnsFrom": [
"qso_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"qsos": {
"name": "qsos",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"callsign": {
"name": "callsign",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"qso_date": {
"name": "qso_date",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_on": {
"name": "time_on",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"band": {
"name": "band",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"mode": {
"name": "mode",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"freq": {
"name": "freq",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"freq_rx": {
"name": "freq_rx",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"entity": {
"name": "entity",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"entity_id": {
"name": "entity_id",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"grid": {
"name": "grid",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"grid_source": {
"name": "grid_source",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"continent": {
"name": "continent",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cq_zone": {
"name": "cq_zone",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"itu_zone": {
"name": "itu_zone",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"state": {
"name": "state",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"county": {
"name": "county",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"sat_name": {
"name": "sat_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"sat_mode": {
"name": "sat_mode",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"my_darc_dok": {
"name": "my_darc_dok",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"darc_dok": {
"name": "darc_dok",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"lotw_qsl_rdate": {
"name": "lotw_qsl_rdate",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"lotw_qsl_rstatus": {
"name": "lotw_qsl_rstatus",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"dcl_qsl_rdate": {
"name": "dcl_qsl_rdate",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"dcl_qsl_rstatus": {
"name": "dcl_qsl_rstatus",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"lotw_synced_at": {
"name": "lotw_synced_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"qsos_user_id_users_id_fk": {
"name": "qsos_user_id_users_id_fk",
"tableFrom": "qsos",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sync_jobs": {
"name": "sync_jobs",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"started_at": {
"name": "started_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"completed_at": {
"name": "completed_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"result": {
"name": "result",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"sync_jobs_user_id_users_id_fk": {
"name": "sync_jobs_user_id_users_id_fk",
"tableFrom": "sync_jobs",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"callsign": {
"name": "callsign",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lotw_username": {
"name": "lotw_username",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"lotw_password": {
"name": "lotw_password",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"dcl_api_key": {
"name": "dcl_api_key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"is_admin": {
"name": "is_admin",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"users_email_unique": {
"name": "users_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -22,6 +22,13 @@
"when": 1768988121232,
"tag": "0002_nervous_layla_miller",
"breakpoints": true
},
{
"idx": 3,
"version": "6",
"when": 1768989260562,
"tag": "0003_tired_warpath",
"breakpoints": true
}
]
}