diff --git a/auth_test.go b/auth_test.go
index 1a16571..f611bc1 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -95,7 +95,7 @@ func TestAuthMiddlewareRoleEnforcement(t *testing.T) {
mux := testMux(app)
// Create a gate user — should not be able to access /api/users (admin only)
- gate := testUserWithRole(t, app, "gateuser", "gate", []int{})
+ gate := testUserWithRole(t, app, "gateuser", "gatekeeper", []int{})
token := testToken(t, app, gate)
req := testAuthRequest("GET", "/api/users", nil, token)
diff --git a/db.go b/db.go
index 3dba412..d93807d 100644
--- a/db.go
+++ b/db.go
@@ -44,7 +44,7 @@ func migrate(db *sql.DB) error {
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
- role TEXT NOT NULL CHECK(role IN ('admin','coordinator','gate','ticketing','volunteer_lead')),
+ role TEXT NOT NULL CHECK(role IN ('admin','ticketing','staffing','colead','gatekeeper')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
@@ -260,6 +260,46 @@ func migrateV3(db *sql.DB) error {
AND v.deleted_at IS NULL
AND NOT EXISTS (SELECT 1 FROM tickets t WHERE t.participant_id = v.participant_id AND t.deleted_at IS NULL)`)
+ return migrateV4(db)
+}
+
+// migrateV4 renames roles: volunteer_lead→colead, coordinator→staffing, gate→gatekeeper.
+func migrateV4(db *sql.DB) error {
+ var count int
+ if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE role IN ('volunteer_lead','coordinator','gate')`).Scan(&count); err != nil || count == 0 {
+ return nil
+ }
+ if _, err := db.Exec(`PRAGMA foreign_keys = OFF`); err != nil {
+ return err
+ }
+ stmts := []string{
+ `CREATE TABLE users_v4 (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL UNIQUE,
+ password_hash TEXT NOT NULL,
+ role TEXT NOT NULL CHECK(role IN ('admin','ticketing','staffing','colead','gatekeeper')),
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ )`,
+ `INSERT INTO users_v4 (id, username, password_hash, role, created_at)
+ SELECT id, username, password_hash,
+ CASE role
+ WHEN 'volunteer_lead' THEN 'colead'
+ WHEN 'coordinator' THEN 'staffing'
+ WHEN 'gate' THEN 'gatekeeper'
+ ELSE role
+ END,
+ created_at
+ FROM users`,
+ `DROP TABLE users`,
+ `ALTER TABLE users_v4 RENAME TO users`,
+ `PRAGMA foreign_keys = ON`,
+ }
+ for _, s := range stmts {
+ if _, err := db.Exec(s); err != nil {
+ db.Exec(`PRAGMA foreign_keys = ON`)
+ return fmt.Errorf("migrateV4: %w", err)
+ }
+ }
return nil
}
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index ba26931..7f28a8a 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -4,7 +4,6 @@
import { syncPull, startSSE, startSyncLoop } from './sync.js'
import Login from './pages/Login.svelte'
import Dashboard from './pages/Dashboard.svelte'
- import Attendees from './pages/Attendees.svelte'
import Participants from './pages/Participants.svelte'
import Volunteers from './pages/Volunteers.svelte'
import Departments from './pages/Departments.svelte'
@@ -104,7 +103,7 @@
{:else if !session}
-{:else if role === 'gate'}
+{:else if role === 'gatekeeper'}
{:else}
@@ -122,13 +121,11 @@
Turnpike
{#if path === '/' || path === ''}
- {#if role === 'volunteer_lead'}
+ {#if role === 'colead'}
{:else}
{/if}
- {:else if path.startsWith('/attendees')}
-
{:else if path.startsWith('/participants')}
{:else if path.startsWith('/volunteers')}
diff --git a/frontend/src/components/Nav.svelte b/frontend/src/components/Nav.svelte
index d7ccc63..545e171 100644
--- a/frontend/src/components/Nav.svelte
+++ b/frontend/src/components/Nav.svelte
@@ -1,5 +1,5 @@