-- Roles for the API's user management (2026-08-05) -- -- `POST /Users/register` used to be anonymous so that the first account could be -- created. It is now `POST /Users/AddUser` and requires the Admin role, so the first -- admin has to be granted here, by hand. -- -- No DDL is needed: applicationrole and applicationuserrole already exist and match -- what Entity Framework now expects (userid, roleid, no surrogate key). The blocks -- below are safe to re-run. -- -- psql "$LADOSE_DB" -v ON_ERROR_STOP=1 -f Sql/2026-08-05_roles.sql SET search_path TO ladoseapi; BEGIN; -- 1. Reference data. These two names are the ones the API knows about -- (LaDOSE.Entity/Roles.cs); only 'Admin' grants anything today. INSERT INTO applicationrole (name) SELECT 'Admin' WHERE NOT EXISTS (SELECT 1 FROM applicationrole WHERE lower(name) = 'admin'); INSERT INTO applicationrole (name) SELECT 'User' WHERE NOT EXISTS (SELECT 1 FROM applicationrole WHERE lower(name) = 'user'); -- 2. Grant Admin to the first administrator. -- >>> Replace 'CHANGEME' with your username before running. <<< -- Existing accounts hold no role until you do this, and a role-less account can -- still use every other endpoint — only user management is restricted. INSERT INTO applicationuserrole (userid, roleid) SELECT u.id, r.id FROM applicationuser u CROSS JOIN applicationrole r WHERE u.username = 'CHANGEME' AND lower(r.name) = 'admin' AND NOT EXISTS ( SELECT 1 FROM applicationuserrole ur WHERE ur.userid = u.id AND ur.roleid = r.id ); COMMIT; -- Who is an admin now? SELECT u.id, u.username, r.name AS role FROM applicationuser u LEFT JOIN applicationuserrole ur ON ur.userid = u.id LEFT JOIN applicationrole r ON r.id = ur.roleid ORDER BY u.username, r.name; -- To take Admin away from someone: -- DELETE FROM applicationuserrole ur -- USING applicationuser u, applicationrole r -- WHERE ur.userid = u.id AND ur.roleid = r.id -- AND u.username = 'CHANGEME' AND lower(r.name) = 'admin'; -- -- The API refuses to delete the last remaining admin, so there is always a way back -- in through the app itself.