Renamed and updated roles and privileges.
This commit is contained in:
parent
cd8e1e3b3b
commit
d30ee18e77
13 changed files with 112 additions and 72 deletions
|
|
@ -95,7 +95,7 @@ func TestAuthMiddlewareRoleEnforcement(t *testing.T) {
|
||||||
mux := testMux(app)
|
mux := testMux(app)
|
||||||
|
|
||||||
// Create a gate user — should not be able to access /api/users (admin only)
|
// 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)
|
token := testToken(t, app, gate)
|
||||||
|
|
||||||
req := testAuthRequest("GET", "/api/users", nil, token)
|
req := testAuthRequest("GET", "/api/users", nil, token)
|
||||||
|
|
|
||||||
42
db.go
42
db.go
|
|
@ -44,7 +44,7 @@ func migrate(db *sql.DB) error {
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
password_hash TEXT NOT NULL,
|
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'))
|
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 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)`)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import { syncPull, startSSE, startSyncLoop } from './sync.js'
|
import { syncPull, startSSE, startSyncLoop } from './sync.js'
|
||||||
import Login from './pages/Login.svelte'
|
import Login from './pages/Login.svelte'
|
||||||
import Dashboard from './pages/Dashboard.svelte'
|
import Dashboard from './pages/Dashboard.svelte'
|
||||||
import Attendees from './pages/Attendees.svelte'
|
|
||||||
import Participants from './pages/Participants.svelte'
|
import Participants from './pages/Participants.svelte'
|
||||||
import Volunteers from './pages/Volunteers.svelte'
|
import Volunteers from './pages/Volunteers.svelte'
|
||||||
import Departments from './pages/Departments.svelte'
|
import Departments from './pages/Departments.svelte'
|
||||||
|
|
@ -104,7 +103,7 @@
|
||||||
<ConfirmEmail />
|
<ConfirmEmail />
|
||||||
{:else if !session}
|
{:else if !session}
|
||||||
<Login onlogin={onLogin} />
|
<Login onlogin={onLogin} />
|
||||||
{:else if role === 'gate'}
|
{:else if role === 'gatekeeper'}
|
||||||
<!-- Gate users get the full-screen GateUI instead of the standard layout -->
|
<!-- Gate users get the full-screen GateUI instead of the standard layout -->
|
||||||
<GateUI {session} {onLogout} />
|
<GateUI {session} {onLogout} />
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -122,13 +121,11 @@
|
||||||
<span class="mobile-brand">Turn<span class="accent">pike</span></span>
|
<span class="mobile-brand">Turn<span class="accent">pike</span></span>
|
||||||
</header>
|
</header>
|
||||||
{#if path === '/' || path === ''}
|
{#if path === '/' || path === ''}
|
||||||
{#if role === 'volunteer_lead'}
|
{#if role === 'colead'}
|
||||||
<ScheduleBoard {session} />
|
<ScheduleBoard {session} />
|
||||||
{:else}
|
{:else}
|
||||||
<Dashboard {session} />
|
<Dashboard {session} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if path.startsWith('/attendees')}
|
|
||||||
<Attendees {session} />
|
|
||||||
{:else if path.startsWith('/participants')}
|
{:else if path.startsWith('/participants')}
|
||||||
<Participants {session} />
|
<Participants {session} />
|
||||||
{:else if path.startsWith('/volunteers')}
|
{:else if path.startsWith('/volunteers')}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { LayoutDashboard, ClipboardCheck, Heart, Hexagon, CalendarDays, Upload, Users, Settings, LogOut, Ticket } from 'lucide-svelte'
|
import { LayoutDashboard, Heart, Hexagon, CalendarDays, Upload, Users, Settings, LogOut, Ticket } from 'lucide-svelte'
|
||||||
|
|
||||||
let { session, active, onLogout, navigate, open = false } = $props()
|
let { session, active, onLogout, navigate, open = false } = $props()
|
||||||
|
|
||||||
|
|
@ -8,17 +8,12 @@
|
||||||
const iconProps = { size: 18, strokeWidth: 1.75 }
|
const iconProps = { size: 18, strokeWidth: 1.75 }
|
||||||
|
|
||||||
const links = $derived.by(() => {
|
const links = $derived.by(() => {
|
||||||
if (role === 'ticketing') return [
|
if (role === 'colead') return [
|
||||||
{ href: '/participants', label: 'Participants', icon: Ticket },
|
|
||||||
{ href: '/attendees', label: 'Attendees', icon: ClipboardCheck },
|
|
||||||
{ href: '/import', label: 'Import', icon: Upload },
|
|
||||||
]
|
|
||||||
if (role === 'volunteer_lead') return [
|
|
||||||
{ href: '/', label: 'Schedule', icon: CalendarDays },
|
{ href: '/', label: 'Schedule', icon: CalendarDays },
|
||||||
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
||||||
{ href: '/departments', label: 'Departments', icon: Hexagon },
|
{ href: '/departments', label: 'Departments', icon: Hexagon },
|
||||||
]
|
]
|
||||||
if (role === 'coordinator') return [
|
if (role === 'staffing') return [
|
||||||
{ href: '/', label: 'Dashboard', icon: LayoutDashboard },
|
{ href: '/', label: 'Dashboard', icon: LayoutDashboard },
|
||||||
{ href: '/schedule', label: 'Schedule', icon: CalendarDays },
|
{ href: '/schedule', label: 'Schedule', icon: CalendarDays },
|
||||||
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
||||||
|
|
@ -27,7 +22,6 @@
|
||||||
return [
|
return [
|
||||||
{ href: '/', label: 'Dashboard', icon: LayoutDashboard },
|
{ href: '/', label: 'Dashboard', icon: LayoutDashboard },
|
||||||
{ href: '/participants', label: 'Participants', icon: Ticket },
|
{ href: '/participants', label: 'Participants', icon: Ticket },
|
||||||
{ href: '/attendees', label: 'Attendees', icon: ClipboardCheck },
|
|
||||||
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
{ href: '/volunteers', label: 'Volunteers', icon: Heart },
|
||||||
{ href: '/departments', label: 'Departments', icon: Hexagon },
|
{ href: '/departments', label: 'Departments', icon: Hexagon },
|
||||||
{ href: '/schedule', label: 'Schedule', icon: CalendarDays },
|
{ href: '/schedule', label: 'Schedule', icon: CalendarDays },
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
let saving = $state(false)
|
let saving = $state(false)
|
||||||
|
|
||||||
const role = $derived(session?.user?.role ?? '')
|
const role = $derived(session?.user?.role ?? '')
|
||||||
const canCreate = $derived(['admin', 'coordinator'].includes(role))
|
const canCreate = $derived(['admin', 'ticketing', 'staffing'].includes(role))
|
||||||
const canDelete = $derived(role === 'admin')
|
const canDelete = $derived(['admin', 'ticketing'].includes(role))
|
||||||
|
|
||||||
const allDepts = liveQuery(() =>
|
const allDepts = liveQuery(() =>
|
||||||
db.departments.filter(d => !d.deleted_at).toArray()
|
db.departments.filter(d => !d.deleted_at).toArray()
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
let assigning = $state(false)
|
let assigning = $state(false)
|
||||||
|
|
||||||
const role = $derived(session?.user?.role ?? '')
|
const role = $derived(session?.user?.role ?? '')
|
||||||
const canManage = $derived(['admin', 'coordinator', 'volunteer_lead'].includes(role))
|
const canManage = $derived(['admin', 'ticketing', 'staffing', 'colead'].includes(role))
|
||||||
const myDeptIDs = $derived(session?.user?.department_ids ?? [])
|
const myDeptIDs = $derived(session?.user?.department_ids ?? [])
|
||||||
|
|
||||||
const allDepts = liveQuery(() =>
|
const allDepts = liveQuery(() =>
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
// Departments visible to this user
|
// Departments visible to this user
|
||||||
const visibleDepts = $derived.by(() => {
|
const visibleDepts = $derived.by(() => {
|
||||||
const depts = $allDepts ?? []
|
const depts = $allDepts ?? []
|
||||||
if (role === 'volunteer_lead') return depts.filter(d => myDeptIDs.includes(d.id))
|
if (role === 'colead') return depts.filter(d => myDeptIDs.includes(d.id))
|
||||||
return depts
|
return depts
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
.then(arr => arr.sort((a, b) => a.name.localeCompare(b.name)))
|
.then(arr => arr.sort((a, b) => a.name.localeCompare(b.name)))
|
||||||
)
|
)
|
||||||
|
|
||||||
const roles = ['admin', 'coordinator', 'ticketing', 'gate', 'volunteer_lead']
|
const roles = ['admin', 'ticketing', 'staffing', 'colead', 'gatekeeper']
|
||||||
|
|
||||||
const me = $derived(session?.user?.id)
|
const me = $derived(session?.user?.id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
let newNote = $state('')
|
let newNote = $state('')
|
||||||
|
|
||||||
const role = $derived(session?.user?.role ?? '')
|
const role = $derived(session?.user?.role ?? '')
|
||||||
const canManage = $derived(['admin', 'coordinator', 'volunteer_lead'].includes(role))
|
const canManage = $derived(['admin', 'ticketing', 'staffing', 'colead'].includes(role))
|
||||||
|
|
||||||
const allVolunteers = liveQuery(() =>
|
const allVolunteers = liveQuery(() =>
|
||||||
db.volunteers.filter(v => !v.deleted_at).toArray()
|
db.volunteers.filter(v => !v.deleted_at).toArray()
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ func TestCheckInAttendeeHandler(t *testing.T) {
|
||||||
|
|
||||||
func TestGateRoleCanCheckIn(t *testing.T) {
|
func TestGateRoleCanCheckIn(t *testing.T) {
|
||||||
app := testApp(t)
|
app := testApp(t)
|
||||||
gate := testUserWithRole(t, app, "gateuser", "gate", []int{})
|
gate := testUserWithRole(t, app, "gateuser", "gatekeeper", []int{})
|
||||||
token := testToken(t, app, gate)
|
token := testToken(t, app, gate)
|
||||||
mux := testMux(app)
|
mux := testMux(app)
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ func TestGateRoleCanCheckIn(t *testing.T) {
|
||||||
|
|
||||||
func TestGateRoleCannotDelete(t *testing.T) {
|
func TestGateRoleCannotDelete(t *testing.T) {
|
||||||
app := testApp(t)
|
app := testApp(t)
|
||||||
gate := testUserWithRole(t, app, "gateuser", "gate", []int{})
|
gate := testUserWithRole(t, app, "gateuser", "gatekeeper", []int{})
|
||||||
token := testToken(t, app, gate)
|
token := testToken(t, app, gate)
|
||||||
mux := testMux(app)
|
mux := testMux(app)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func TestResetAttendees(t *testing.T) {
|
||||||
|
|
||||||
func TestResetAttendeesRequiresAdmin(t *testing.T) {
|
func TestResetAttendeesRequiresAdmin(t *testing.T) {
|
||||||
app := testApp(t)
|
app := testApp(t)
|
||||||
gate := testUserWithRole(t, app, "gate1", "gate", []int{})
|
gate := testUserWithRole(t, app, "gate1", "gatekeeper", []int{})
|
||||||
token := testToken(t, app, gate)
|
token := testToken(t, app, gate)
|
||||||
mux := testMux(app)
|
mux := testMux(app)
|
||||||
|
|
||||||
|
|
@ -129,7 +129,7 @@ func TestResetDepartmentsCascadesShifts(t *testing.T) {
|
||||||
|
|
||||||
func TestSettingsNonAdminRejected(t *testing.T) {
|
func TestSettingsNonAdminRejected(t *testing.T) {
|
||||||
app := testApp(t)
|
app := testApp(t)
|
||||||
gate := testUserWithRole(t, app, "gateuser", "gate", []int{})
|
gate := testUserWithRole(t, app, "gateuser", "gatekeeper", []int{})
|
||||||
token := testToken(t, app, gate)
|
token := testToken(t, app, gate)
|
||||||
mux := testMux(app)
|
mux := testMux(app)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func (app *App) handleListShifts(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" && deptID == nil && len(claims.DeptIDs) > 0 {
|
if claims.Role == "colead" && deptID == nil && len(claims.DeptIDs) > 0 {
|
||||||
deptID = &claims.DeptIDs[0]
|
deptID = &claims.DeptIDs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ func (app *App) handleCreateShift(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" && !inSlice(s.DepartmentID, claims.DeptIDs) {
|
if claims.Role == "colead" && !inSlice(s.DepartmentID, claims.DeptIDs) {
|
||||||
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ func (app *App) handleUpdateShift(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" {
|
if claims.Role == "colead" {
|
||||||
existing, _ := app.getShift(id)
|
existing, _ := app.getShift(id)
|
||||||
if existing == nil || !inSlice(existing.DepartmentID, claims.DeptIDs) {
|
if existing == nil || !inSlice(existing.DepartmentID, claims.DeptIDs) {
|
||||||
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func (app *App) handleListVolunteers(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" && deptID == nil && len(claims.DeptIDs) > 0 {
|
if claims.Role == "colead" && deptID == nil && len(claims.DeptIDs) > 0 {
|
||||||
deptID = &claims.DeptIDs[0]
|
deptID = &claims.DeptIDs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,12 +43,21 @@ func (app *App) handleCreateVolunteer(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" {
|
if claims.Role == "colead" {
|
||||||
if v.DepartmentID == nil || !inSlice(*v.DepartmentID, claims.DeptIDs) {
|
if v.DepartmentID == nil || !inSlice(*v.DepartmentID, claims.DeptIDs) {
|
||||||
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v.Email != "" && v.ParticipantID == nil {
|
||||||
|
p, _ := app.getParticipantByEmail(v.Email)
|
||||||
|
if p == nil {
|
||||||
|
p, _ = app.createParticipant(Participant{PreferredName: v.Name, Email: v.Email})
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
v.ParticipantID = &p.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
created, err := app.createVolunteer(v)
|
created, err := app.createVolunteer(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
@ -88,7 +97,7 @@ func (app *App) handleUpdateVolunteer(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims := claimsFromContext(r)
|
claims := claimsFromContext(r)
|
||||||
if claims.Role == "volunteer_lead" {
|
if claims.Role == "colead" {
|
||||||
existing, _ := app.getVolunteer(id)
|
existing, _ := app.getVolunteer(id)
|
||||||
if existing == nil || existing.DepartmentID == nil || !inSlice(*existing.DepartmentID, claims.DeptIDs) {
|
if existing == nil || existing.DepartmentID == nil || !inSlice(*existing.DepartmentID, claims.DeptIDs) {
|
||||||
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
writeError(w, "forbidden: outside your department", http.StatusForbidden)
|
||||||
|
|
|
||||||
80
main.go
80
main.go
|
|
@ -97,71 +97,71 @@ func (app *App) registerRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("GET /api/me", auth(app.handleMe))
|
mux.HandleFunc("GET /api/me", auth(app.handleMe))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/event", auth(app.handleGetEvent))
|
mux.HandleFunc("GET /api/event", auth(app.handleGetEvent))
|
||||||
mux.HandleFunc("PUT /api/event", auth(app.handleUpdateEvent, "admin"))
|
mux.HandleFunc("PUT /api/event", auth(app.handleUpdateEvent, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/attendees", auth(app.handleListAttendees, "admin", "ticketing", "gate"))
|
mux.HandleFunc("GET /api/attendees", auth(app.handleListAttendees, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("POST /api/attendees", auth(app.handleCreateAttendee, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/attendees", auth(app.handleCreateAttendee, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/attendees/export", auth(app.handleExportAttendees, "admin", "ticketing"))
|
mux.HandleFunc("GET /api/attendees/export", auth(app.handleExportAttendees, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/attendees/generate-tokens", auth(app.handleGenerateTokens, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/attendees/generate-tokens", auth(app.handleGenerateTokens, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/attendees/export-tokens", auth(app.handleExportTokenLinks, "admin", "ticketing"))
|
mux.HandleFunc("GET /api/attendees/export-tokens", auth(app.handleExportTokenLinks, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/attendees/email-tokens", auth(app.handleEmailAllTokens, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/attendees/email-tokens", auth(app.handleEmailAllTokens, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/attendees/{id}", auth(app.handleGetAttendee, "admin", "ticketing", "gate"))
|
mux.HandleFunc("GET /api/attendees/{id}", auth(app.handleGetAttendee, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("PUT /api/attendees/{id}", auth(app.handleUpdateAttendee, "admin", "ticketing"))
|
mux.HandleFunc("PUT /api/attendees/{id}", auth(app.handleUpdateAttendee, "admin", "ticketing"))
|
||||||
mux.HandleFunc("DELETE /api/attendees/{id}", auth(app.handleDeleteAttendee, "admin", "ticketing"))
|
mux.HandleFunc("DELETE /api/attendees/{id}", auth(app.handleDeleteAttendee, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/attendees/{id}/checkin", auth(app.handleCheckInAttendee, "admin", "ticketing", "gate"))
|
mux.HandleFunc("POST /api/attendees/{id}/checkin", auth(app.handleCheckInAttendee, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("POST /api/attendees/{id}/email-token", auth(app.handleEmailToken, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/attendees/{id}/email-token", auth(app.handleEmailToken, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/participants", auth(app.handleListParticipants, "admin", "ticketing", "gate"))
|
mux.HandleFunc("GET /api/participants", auth(app.handleListParticipants, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("POST /api/participants", auth(app.handleCreateParticipant, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/participants", auth(app.handleCreateParticipant, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/participants/export", auth(app.handleExportParticipants, "admin", "ticketing"))
|
mux.HandleFunc("GET /api/participants/export", auth(app.handleExportParticipants, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/participants/{id}", auth(app.handleGetParticipant, "admin", "ticketing", "gate"))
|
mux.HandleFunc("GET /api/participants/{id}", auth(app.handleGetParticipant, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("PUT /api/participants/{id}", auth(app.handleUpdateParticipant, "admin", "ticketing"))
|
mux.HandleFunc("PUT /api/participants/{id}", auth(app.handleUpdateParticipant, "admin", "ticketing"))
|
||||||
mux.HandleFunc("DELETE /api/participants/{id}", auth(app.handleDeleteParticipant, "admin", "ticketing"))
|
mux.HandleFunc("DELETE /api/participants/{id}", auth(app.handleDeleteParticipant, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/participants/{id}/merge/{other_id}", auth(app.handleMergeParticipants, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/participants/{id}/merge/{other_id}", auth(app.handleMergeParticipants, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/tickets", auth(app.handleListTickets, "admin", "ticketing", "gate"))
|
mux.HandleFunc("GET /api/tickets", auth(app.handleListTickets, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("POST /api/tickets/{id}/checkin", auth(app.handleCheckInTicket, "admin", "ticketing", "gate"))
|
mux.HandleFunc("POST /api/tickets/{id}/checkin", auth(app.handleCheckInTicket, "admin", "ticketing", "gatekeeper"))
|
||||||
mux.HandleFunc("POST /api/tickets/generate-codes", auth(app.handleGenerateTokens, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/tickets/generate-codes", auth(app.handleGenerateTokens, "admin", "ticketing"))
|
||||||
mux.HandleFunc("GET /api/tickets/export-links", auth(app.handleExportTokenLinks, "admin", "ticketing"))
|
mux.HandleFunc("GET /api/tickets/export-links", auth(app.handleExportTokenLinks, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/tickets/email-codes", auth(app.handleEmailAllTokens, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/tickets/email-codes", auth(app.handleEmailAllTokens, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/tickets/{id}/email-code", auth(app.handleEmailToken, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/tickets/{id}/email-code", auth(app.handleEmailToken, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/departments", auth(app.handleListDepartments))
|
mux.HandleFunc("GET /api/departments", auth(app.handleListDepartments))
|
||||||
mux.HandleFunc("POST /api/departments", auth(app.handleCreateDepartment, "admin", "coordinator"))
|
mux.HandleFunc("POST /api/departments", auth(app.handleCreateDepartment, "admin", "ticketing", "staffing"))
|
||||||
mux.HandleFunc("PUT /api/departments/{id}", auth(app.handleUpdateDepartment, "admin", "coordinator"))
|
mux.HandleFunc("PUT /api/departments/{id}", auth(app.handleUpdateDepartment, "admin", "ticketing", "staffing"))
|
||||||
mux.HandleFunc("DELETE /api/departments/{id}", auth(app.handleDeleteDepartment, "admin"))
|
mux.HandleFunc("DELETE /api/departments/{id}", auth(app.handleDeleteDepartment, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/volunteers", auth(app.handleListVolunteers, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("GET /api/volunteers", auth(app.handleListVolunteers, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/volunteers", auth(app.handleCreateVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/volunteers", auth(app.handleCreateVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("GET /api/volunteers/{id}", auth(app.handleGetVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("GET /api/volunteers/{id}", auth(app.handleGetVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("PUT /api/volunteers/{id}", auth(app.handleUpdateVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("PUT /api/volunteers/{id}", auth(app.handleUpdateVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("DELETE /api/volunteers/{id}", auth(app.handleDeleteVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("DELETE /api/volunteers/{id}", auth(app.handleDeleteVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/volunteers/{id}/checkin", auth(app.handleCheckInVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/volunteers/{id}/checkin", auth(app.handleCheckInVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/volunteers/{id}/shifts", auth(app.handleAssignShift, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/volunteers/{id}/shifts", auth(app.handleAssignShift, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("DELETE /api/volunteers/{id}/shifts/{shift_id}", auth(app.handleUnassignShift, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("DELETE /api/volunteers/{id}/shifts/{shift_id}", auth(app.handleUnassignShift, "admin", "ticketing", "staffing", "colead"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/shifts", auth(app.handleListShifts, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("GET /api/shifts", auth(app.handleListShifts, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/shifts", auth(app.handleCreateShift, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/shifts", auth(app.handleCreateShift, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/shifts/reorder", auth(app.handleReorderShifts, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/shifts/reorder", auth(app.handleReorderShifts, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("PUT /api/shifts/{id}", auth(app.handleUpdateShift, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("PUT /api/shifts/{id}", auth(app.handleUpdateShift, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("DELETE /api/shifts/{id}", auth(app.handleDeleteShift, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("DELETE /api/shifts/{id}", auth(app.handleDeleteShift, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("POST /api/shifts/{id}/volunteers", auth(app.handleAssignShiftVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("POST /api/shifts/{id}/volunteers", auth(app.handleAssignShiftVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
mux.HandleFunc("DELETE /api/shifts/{id}/volunteers/{volunteer_id}", auth(app.handleUnassignShiftVolunteer, "admin", "coordinator", "volunteer_lead"))
|
mux.HandleFunc("DELETE /api/shifts/{id}/volunteers/{volunteer_id}", auth(app.handleUnassignShiftVolunteer, "admin", "ticketing", "staffing", "colead"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/users", auth(app.handleListUsers, "admin"))
|
mux.HandleFunc("GET /api/users", auth(app.handleListUsers, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/users", auth(app.handleCreateUser, "admin"))
|
mux.HandleFunc("POST /api/users", auth(app.handleCreateUser, "admin", "ticketing"))
|
||||||
mux.HandleFunc("PUT /api/users/{id}", auth(app.handleUpdateUser, "admin"))
|
mux.HandleFunc("PUT /api/users/{id}", auth(app.handleUpdateUser, "admin", "ticketing"))
|
||||||
mux.HandleFunc("DELETE /api/users/{id}", auth(app.handleDeleteUser, "admin"))
|
mux.HandleFunc("DELETE /api/users/{id}", auth(app.handleDeleteUser, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("GET /api/settings", auth(app.handleGetSettings, "admin"))
|
mux.HandleFunc("GET /api/settings", auth(app.handleGetSettings, "admin", "ticketing"))
|
||||||
mux.HandleFunc("PUT /api/settings", auth(app.handleUpdateSettings, "admin"))
|
mux.HandleFunc("PUT /api/settings", auth(app.handleUpdateSettings, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/test-email", auth(app.handleTestEmail, "admin"))
|
mux.HandleFunc("POST /api/settings/test-email", auth(app.handleTestEmail, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-attendees", auth(app.handleResetAttendees, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-attendees", auth(app.handleResetAttendees, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-tickets", auth(app.handleResetTickets, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-tickets", auth(app.handleResetTickets, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-volunteers", auth(app.handleResetVolunteers, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-volunteers", auth(app.handleResetVolunteers, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-shifts", auth(app.handleResetShifts, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-shifts", auth(app.handleResetShifts, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-departments", auth(app.handleResetDepartments, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-departments", auth(app.handleResetDepartments, "admin", "ticketing"))
|
||||||
mux.HandleFunc("POST /api/settings/reset-volunteer-shifts", auth(app.handleResetVolunteerShifts, "admin"))
|
mux.HandleFunc("POST /api/settings/reset-volunteer-shifts", auth(app.handleResetVolunteerShifts, "admin", "ticketing"))
|
||||||
|
|
||||||
mux.HandleFunc("POST /api/import", auth(app.handleImport, "admin", "ticketing"))
|
mux.HandleFunc("POST /api/import", auth(app.handleImport, "admin", "ticketing"))
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ func (app *App) registerRoutes(mux *http.ServeMux) {
|
||||||
writeJSON(w, map[string]string{"build": buildID})
|
writeJSON(w, map[string]string{"build": buildID})
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("POST /api/settings/shift-signups", auth(app.handleToggleShiftSignups, "admin", "volunteer_lead"))
|
mux.HandleFunc("POST /api/settings/shift-signups", auth(app.handleToggleShiftSignups, "admin", "ticketing", "staffing"))
|
||||||
|
|
||||||
// Public endpoints — no JWT required.
|
// Public endpoints — no JWT required.
|
||||||
mux.HandleFunc("GET /api/public/signup-config", app.handlePublicSignupConfig)
|
mux.HandleFunc("GET /api/public/signup-config", app.handlePublicSignupConfig)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue