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:
1
drizzle/0003_tired_warpath.sql
Normal file
1
drizzle/0003_tired_warpath.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `users` DROP COLUMN `role`;
|
||||
748
drizzle/meta/0003_snapshot.json
Normal file
748
drizzle/meta/0003_snapshot.json
Normal 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": {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||||
* @property {string|null} lotwUsername
|
||||
* @property {string|null} lotwPassword
|
||||
* @property {string|null} dclApiKey
|
||||
* @property {string} role
|
||||
* @property {boolean} isAdmin
|
||||
* @property {Date} createdAt
|
||||
* @property {Date} updatedAt
|
||||
@@ -23,8 +22,7 @@ export const users = sqliteTable('users', {
|
||||
lotwUsername: text('lotw_username'),
|
||||
lotwPassword: text('lotw_password'), // Encrypted
|
||||
dclApiKey: text('dcl_api_key'), // DCL API key for future use
|
||||
role: text('role').notNull().default('user'), // 'user', 'admin'
|
||||
isAdmin: integer('is_admin', { mode: 'boolean' }).notNull().default(false), // Simplified admin check
|
||||
isAdmin: integer('is_admin', { mode: 'boolean' }).notNull().default(false),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
@@ -195,7 +195,6 @@ const app = new Elysia()
|
||||
id: payload.userId,
|
||||
email: payload.email,
|
||||
callsign: payload.callsign,
|
||||
role: payload.role,
|
||||
isAdmin: payload.isAdmin,
|
||||
impersonatedBy: payload.impersonatedBy, // Admin ID if impersonating
|
||||
},
|
||||
@@ -400,7 +399,6 @@ const app = new Elysia()
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
callsign: user.callsign,
|
||||
role: user.role,
|
||||
isAdmin: user.isAdmin,
|
||||
exp,
|
||||
});
|
||||
@@ -1139,7 +1137,7 @@ const app = new Elysia()
|
||||
|
||||
/**
|
||||
* POST /api/admin/users/:userId/role
|
||||
* Update user role (admin only)
|
||||
* Update user admin status (admin only)
|
||||
*/
|
||||
.post('/api/admin/users/:userId/role', async ({ user, params, body, set }) => {
|
||||
if (!user || !user.isAdmin) {
|
||||
@@ -1153,26 +1151,21 @@ const app = new Elysia()
|
||||
return { success: false, error: 'Invalid user ID' };
|
||||
}
|
||||
|
||||
const { role, isAdmin } = body;
|
||||
const { isAdmin } = body;
|
||||
|
||||
if (!role || typeof isAdmin !== 'boolean') {
|
||||
if (typeof isAdmin !== 'boolean') {
|
||||
set.status = 400;
|
||||
return { success: false, error: 'role and isAdmin are required' };
|
||||
}
|
||||
|
||||
if (!['user', 'admin'].includes(role)) {
|
||||
set.status = 400;
|
||||
return { success: false, error: 'Invalid role' };
|
||||
return { success: false, error: 'isAdmin (boolean) is required' };
|
||||
}
|
||||
|
||||
try {
|
||||
await changeUserRole(user.id, targetUserId, role, isAdmin);
|
||||
await changeUserRole(user.id, targetUserId, isAdmin);
|
||||
return {
|
||||
success: true,
|
||||
message: 'User role updated successfully',
|
||||
message: 'User admin status updated successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error updating user role', { error: error.message, userId: user.id });
|
||||
logger.error('Error updating user admin status', { error: error.message, userId: user.id });
|
||||
set.status = 400;
|
||||
return {
|
||||
success: false,
|
||||
@@ -1238,7 +1231,6 @@ const app = new Elysia()
|
||||
userId: targetUser.id,
|
||||
email: targetUser.email,
|
||||
callsign: targetUser.callsign,
|
||||
role: targetUser.role,
|
||||
isAdmin: targetUser.isAdmin,
|
||||
impersonatedBy: user.id, // Admin ID who started impersonation
|
||||
exp,
|
||||
@@ -1299,7 +1291,6 @@ const app = new Elysia()
|
||||
userId: adminUser.id,
|
||||
email: adminUser.email,
|
||||
callsign: adminUser.callsign,
|
||||
role: adminUser.role,
|
||||
isAdmin: adminUser.isAdmin,
|
||||
exp,
|
||||
});
|
||||
|
||||
@@ -69,15 +69,14 @@ function createAdminUser(email, password, callsign) {
|
||||
|
||||
// Insert admin user
|
||||
const result = sqlite.query(`
|
||||
INSERT INTO users (email, password_hash, callsign, role, is_admin, created_at, updated_at)
|
||||
VALUES (?, ?, ?, 'admin', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000)
|
||||
INSERT INTO users (email, password_hash, callsign, is_admin, created_at, updated_at)
|
||||
VALUES (?, ?, ?, 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000)
|
||||
`).run(email, hashString, callsign);
|
||||
|
||||
console.log(`✓ Admin user created successfully!`);
|
||||
console.log(` ID: ${result.lastInsertRowid}`);
|
||||
console.log(` Email: ${email}`);
|
||||
console.log(` Callsign: ${callsign}`);
|
||||
console.log(` Role: admin`);
|
||||
console.log(`\nYou can now log in with these credentials.`);
|
||||
}
|
||||
|
||||
@@ -86,7 +85,7 @@ function promoteUser(email) {
|
||||
|
||||
// Check if user exists
|
||||
const user = sqlite.query(`
|
||||
SELECT id, email, role, is_admin FROM users WHERE email = ?
|
||||
SELECT id, email, is_admin FROM users WHERE email = ?
|
||||
`).get(email);
|
||||
|
||||
if (!user) {
|
||||
@@ -94,7 +93,7 @@ function promoteUser(email) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (user.role === 'admin' && user.is_admin === 1) {
|
||||
if (user.is_admin === 1) {
|
||||
console.log(`User ${email} is already an admin`);
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +101,7 @@ function promoteUser(email) {
|
||||
// Update user to admin
|
||||
sqlite.query(`
|
||||
UPDATE users
|
||||
SET role = 'admin', is_admin = 1, updated_at = strftime('%s', 'now') * 1000
|
||||
SET is_admin = 1, updated_at = strftime('%s', 'now') * 1000
|
||||
WHERE email = ?
|
||||
`).run(email);
|
||||
|
||||
@@ -114,7 +113,7 @@ function demoteUser(email) {
|
||||
|
||||
// Check if user exists
|
||||
const user = sqlite.query(`
|
||||
SELECT id, email, role, is_admin FROM users WHERE email = ?
|
||||
SELECT id, email, is_admin FROM users WHERE email = ?
|
||||
`).get(email);
|
||||
|
||||
if (!user) {
|
||||
@@ -122,14 +121,14 @@ function demoteUser(email) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (user.role !== 'admin' || user.is_admin !== 1) {
|
||||
if (user.is_admin !== 1) {
|
||||
console.log(`User ${email} is not an admin`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the last admin
|
||||
const adminCount = sqlite.query(`
|
||||
SELECT COUNT(*) as count FROM users WHERE role = 'admin' AND is_admin = 1
|
||||
SELECT COUNT(*) as count FROM users WHERE is_admin = 1
|
||||
`).get();
|
||||
|
||||
if (adminCount.count === 1) {
|
||||
@@ -140,7 +139,7 @@ function demoteUser(email) {
|
||||
// Update user to regular user
|
||||
sqlite.query(`
|
||||
UPDATE users
|
||||
SET role = 'user', is_admin = 0, updated_at = strftime('%s', 'now') * 1000
|
||||
SET is_admin = 0, updated_at = strftime('%s', 'now') * 1000
|
||||
WHERE email = ?
|
||||
`).run(email);
|
||||
|
||||
@@ -153,7 +152,7 @@ function listAdmins() {
|
||||
const admins = sqlite.query(`
|
||||
SELECT id, email, callsign, created_at
|
||||
FROM users
|
||||
WHERE role = 'admin' AND is_admin = 1
|
||||
WHERE is_admin = 1
|
||||
ORDER BY created_at ASC
|
||||
`).all();
|
||||
|
||||
@@ -176,7 +175,7 @@ function checkUser(email) {
|
||||
console.log(`Checking user status: ${email}\n`);
|
||||
|
||||
const user = sqlite.query(`
|
||||
SELECT id, email, callsign, role, is_admin FROM users WHERE email = ?
|
||||
SELECT id, email, callsign, is_admin FROM users WHERE email = ?
|
||||
`).get(email);
|
||||
|
||||
if (!user) {
|
||||
@@ -184,12 +183,11 @@ function checkUser(email) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isAdmin = user.role === 'admin' && user.is_admin === 1;
|
||||
const isAdmin = user.is_admin === 1;
|
||||
|
||||
console.log(`User found:`);
|
||||
console.log(` Email: ${user.email}`);
|
||||
console.log(` Callsign: ${user.callsign}`);
|
||||
console.log(` Role: ${user.role}`);
|
||||
console.log(` Is Admin: ${isAdmin ? 'Yes ✓' : 'No'}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ export async function getUserStats() {
|
||||
id: users.id,
|
||||
email: users.email,
|
||||
callsign: users.callsign,
|
||||
role: users.role,
|
||||
isAdmin: users.isAdmin,
|
||||
qsoCount: sql`CAST(COUNT(${qsos.id}) AS INTEGER)`,
|
||||
lotwConfirmed: sql`CAST(SUM(CASE WHEN ${qsos.lotwQslRstatus} = 'Y' THEN 1 ELSE 0 END) AS INTEGER)`,
|
||||
@@ -250,19 +249,18 @@ export async function getImpersonationStatus(adminId, { limit = 10 } = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user role (admin operation)
|
||||
* Update user admin status (admin operation)
|
||||
* @param {number} adminId - Admin user ID making the change
|
||||
* @param {number} targetUserId - User ID to update
|
||||
* @param {string} newRole - New role ('user' or 'admin')
|
||||
* @param {boolean} newIsAdmin - New admin flag
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If not admin or would remove last admin
|
||||
*/
|
||||
export async function changeUserRole(adminId, targetUserId, newRole, newIsAdmin) {
|
||||
export async function changeUserRole(adminId, targetUserId, newIsAdmin) {
|
||||
// Verify the requester is an admin
|
||||
const requesterIsAdmin = await isAdmin(adminId);
|
||||
if (!requesterIsAdmin) {
|
||||
throw new Error('Only admins can change user roles');
|
||||
throw new Error('Only admins can change user admin status');
|
||||
}
|
||||
|
||||
// Get target user
|
||||
@@ -283,11 +281,10 @@ export async function changeUserRole(adminId, targetUserId, newRole, newIsAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
// Update role
|
||||
// Update admin status
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
role: newRole,
|
||||
isAdmin: newIsAdmin ? 1 : 0,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
@@ -295,8 +292,6 @@ export async function changeUserRole(adminId, targetUserId, newRole, newIsAdmin)
|
||||
|
||||
// Log action
|
||||
await logAdminAction(adminId, 'role_change', targetUserId, {
|
||||
oldRole: targetUser.role,
|
||||
newRole: newRole,
|
||||
oldIsAdmin: targetUser.isAdmin,
|
||||
newIsAdmin: newIsAdmin,
|
||||
});
|
||||
|
||||
@@ -168,7 +168,6 @@ export async function getAdminUsers() {
|
||||
id: users.id,
|
||||
email: users.email,
|
||||
callsign: users.callsign,
|
||||
role: users.role,
|
||||
isAdmin: users.isAdmin,
|
||||
createdAt: users.createdAt,
|
||||
})
|
||||
@@ -179,17 +178,15 @@ export async function getAdminUsers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user role
|
||||
* Update user admin status
|
||||
* @param {number} userId - User ID
|
||||
* @param {string} role - New role ('user' or 'admin')
|
||||
* @param {boolean} isAdmin - Admin flag
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function updateUserRole(userId, role, isAdmin) {
|
||||
export async function updateUserRole(userId, isAdmin) {
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
role,
|
||||
isAdmin: isAdmin ? 1 : 0,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
@@ -206,7 +203,6 @@ export async function getAllUsers() {
|
||||
id: users.id,
|
||||
email: users.email,
|
||||
callsign: users.callsign,
|
||||
role: users.role,
|
||||
isAdmin: users.isAdmin,
|
||||
createdAt: users.createdAt,
|
||||
updatedAt: users.updatedAt,
|
||||
@@ -228,7 +224,6 @@ export async function getUserByIdFull(userId) {
|
||||
id: users.id,
|
||||
email: users.email,
|
||||
callsign: users.callsign,
|
||||
role: users.role,
|
||||
isAdmin: users.isAdmin,
|
||||
lotwUsername: users.lotwUsername,
|
||||
dclApiKey: users.dclApiKey,
|
||||
|
||||
@@ -95,9 +95,9 @@ export const adminAPI = {
|
||||
|
||||
getUserDetails: (userId) => apiRequest(`/admin/users/${userId}`),
|
||||
|
||||
updateUserRole: (userId, role, isAdmin) => apiRequest(`/admin/users/${userId}/role`, {
|
||||
updateUserRole: (userId, isAdmin) => apiRequest(`/admin/users/${userId}/role`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ role, isAdmin }),
|
||||
body: JSON.stringify({ isAdmin }),
|
||||
}),
|
||||
|
||||
deleteUser: (userId) => apiRequest(`/admin/users/${userId}`, {
|
||||
|
||||
@@ -167,19 +167,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRoleChange(userId, newRole, newIsAdmin) {
|
||||
async function handleRoleChange(userId, newIsAdmin) {
|
||||
try {
|
||||
loading = true;
|
||||
const data = await adminAPI.updateUserRole(userId, newRole, newIsAdmin);
|
||||
const data = await adminAPI.updateUserRole(userId, newIsAdmin);
|
||||
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
await loadUsers();
|
||||
} else {
|
||||
alert('Failed to update user role: ' + (data.error || 'Unknown error'));
|
||||
alert('Failed to update user admin status: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Failed to update user role: ' + err.message);
|
||||
alert('Failed to update user admin status: ' + err.message);
|
||||
} finally {
|
||||
loading = false;
|
||||
showRoleChangeModal = false;
|
||||
@@ -518,7 +518,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="modal-button confirm"
|
||||
on:click={() => handleRoleChange(selectedUser.userId, selectedUser.isAdmin ? 'user' : 'admin', !selectedUser.isAdmin)}
|
||||
on:click={() => handleRoleChange(selectedUser.userId, !selectedUser.isAdmin)}
|
||||
>
|
||||
Change Role
|
||||
</button>
|
||||
|
||||
@@ -25,14 +25,12 @@
|
||||
try {
|
||||
loading = true;
|
||||
const response = await authAPI.getProfile();
|
||||
console.log('Loaded profile:', response.user);
|
||||
if (response.user) {
|
||||
lotwUsername = response.user.lotwUsername || '';
|
||||
lotwPassword = ''; // Never pre-fill password for security
|
||||
hasLoTWCredentials = !!(response.user.lotwUsername && response.user.lotwPassword);
|
||||
dclApiKey = response.user.dclApiKey || '';
|
||||
hasDCLCredentials = !!response.user.dclApiKey;
|
||||
console.log('Has LoTW credentials:', hasLoTWCredentials, 'Has DCL credentials:', hasDCLCredentials);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load profile:', err);
|
||||
@@ -50,8 +48,6 @@
|
||||
error = null;
|
||||
successLoTW = false;
|
||||
|
||||
console.log('Saving LoTW credentials:', { lotwUsername, hasPassword: !!lotwPassword });
|
||||
|
||||
await authAPI.updateLoTWCredentials({
|
||||
lotwUsername,
|
||||
lotwPassword
|
||||
@@ -78,8 +74,6 @@
|
||||
error = null;
|
||||
successDCL = false;
|
||||
|
||||
console.log('Saving DCL credentials:', { hasApiKey: !!dclApiKey });
|
||||
|
||||
await authAPI.updateDCLCredentials({
|
||||
dclApiKey
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user