begin;

create table lsmb_roles (
    
    user_id integer not null references users,
    role text not null
    
);

CREATE OR REPLACE FUNCTION admin__add_user_to_role(in_user TEXT, in_role TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_user name;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions of a non-existant role.';
        END IF;
        
        select rolname into a_user from pg_roles where rolname = in_user;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions to a non-existant user.';
        END IF;
        
        stmt := 'GRANT '|| quote_ident(in_role) ||' to '|| quote_ident(in_user);
        
        EXECUTE stmt;
        insert into lsmb_roles (user_id, role) values (in_user, in_role);
        return 1;
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__remove_user_from_role(in_user TEXT, in_role TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_user name;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions of a non-existant role.';
        END IF;
        
        select rolname into a_user from pg_roles where rolname = in_user;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions from a non-existant user.';
        END IF;
        
        stmt := 'REVOKE '|| quote_ident(in_role) ||' FROM '|| quote_ident(in_user);
        
        EXECUTE stmt;
        
        return 1;    
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__add_function_to_group(in_func TEXT, in_role TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_user name;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions of a non-existant role.';
        END IF;
        
        select rolname into a_user from pg_roles where rolname = in_user;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions to a non-existant user.';
        END IF;
        
        stmt := 'GRANT EXECUTE ON FUNCTION '|| quote_ident(in_func) ||' to '|| quote_ident(in_role);
        
        EXECUTE stmt;
        
        return 1;
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__remove_function_from_group(in_func TEXT, in_role TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_user name;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions of a non-existant role.';
        END IF;
        
        select rolname into a_user from pg_roles where rolname = in_user;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions from a non-existant function.';
        END IF;
        
        stmt := 'REVOKE EXECUTE ON FUNCTION '|| quote_ident(in_func) ||' FROM '|| quote_ident(in_role);
        
        EXECUTE stmt;
        
        return 1;    
    END;
    
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__add_table_to_group(in_table TEXT, in_role TEXT, in_perm TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_user name;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions of a non-existant role.';
        END IF;
        
        select table_name into a_table from information_schema.tables 
        where table_schema NOT IN ('information_schema','pg_catalog','pg_toast') 
        and table_type='BASE TABLE' 
        and table_name = in_table;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot grant permissions to a non-existant table.';
        END IF;
        
        if lower(in_perm) not in ('select','insert','update','delete') THEN
            raise exception 'Cannot add unknown permission';
        END IF;
        
        stmt := 'GRANT '|| quote_ident(in_perm) || 'ON TABLE '|| quote_ident(in_table) ||' to '|| quote_ident(in_role);
        
        EXECUTE stmt;
        
        return 1;
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__remove_table_from_group(in_table TEXT, in_role TEXT) returns INT AS $$
    
    declare
        stmt TEXT;
        a_role name;
        a_table text;
    BEGIN
    
        -- Issue the grant
        select rolname into a_role from pg_roles where rolname = in_role;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions of a non-existant role.';
        END IF;
        
        select table_name into a_table from information_schema.tables 
        where table_schema NOT IN ('information_schema','pg_catalog','pg_toast') 
        and table_type='BASE TABLE' 
        and table_name = in_table;
        
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Cannot revoke permissions from a non-existant table.';
        END IF;
        
        stmt := 'REVOKE '|| quote_literal(in_role) ||' FROM '|| quote_literal(in_user);
        
        EXECUTE stmt;
        
        return 1;    
    END;
        
$$ language 'plpgsql';

create or replace function admin__get_user(in_user TEXT) returns setof users as $$
    
    DECLARE
        a_user users;
    BEGIN
        
        select * into a_user from users where username = in_user;
        IF NOT FOUND THEN
            RAISE EXCEPTION 'cannot find user %', in_user;
        END IF;
        
        return next a_user;
        return;
    
    END;    
$$ language plpgsql;

create or replace function admin__get_roles_for_user(in_user TEXT) returns setof lsmb_roles as $$
    
    declare
        u_role lsmb_roles;
        a_user users;
    begin
        select * into a_user from admin_get_user(in_user);
        
        FOR u_role IN select * from lsmb_roles lr WHERE lr.user_id = a_user.id LOOP
        
            RETURN NEXT u_role;
        
        END LOOP;
        RETURN;
    end;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__save_user(
    in_id int, 
    in_entity_id INT,
    in_username text, 
    in_password TEXT
) returns int AS $$
    DECLARE
    
        a_user users;
        v_user_id int;
        p_id int;
        l_id int;
        stmt text;
    BEGIN
    
        select * into a_user from users lu where lu.id = in_id;
        
        IF NOT FOUND THEN 
            -- Insert cycle
            
            --- The entity is expected to already BE created. See admin.pm.
            
            if admin__is_user(in_username) then
                
                -- uhm, this is bad.
                RAISE EXCEPTION 'Fatal exception: Username already exists in Postgres; not
                    a valid lsmb user.';
            end if;         
            -- create an actual user
            
            v_user_id := nextval('users_id_seq');
            insert into users (id, username, entity_id) VALUES (
                v_user_id,
                in_username,
                in_entity_id
            );
            
            insert into user_preference (id) values (v_user_id);

            -- Finally, issue the create user statement
            
            stmt := 'CREATE USER ' || quote_ident( in_username ) || ' WITH ENCRYPTED PASSWORD ' || quote_literal (in_password);
            execute stmt;
            
            return v_user_id ;

        ELSIF FOUND THEN
            
            -- update cycle
            
            stmt := ' alter user '|| quote_ident(in_username) || ' with encrypted password ' || quote_literal(in_password);
            execute stmt;
                      
            return a_user.id;
        
        END IF;
    
    END;
$$ language 'plpgsql';

create view role_view as 
    select * from pg_auth_members m join pg_authid a ON (m.roleid = a.oid);
        

create or replace function admin__is_group(in_group_name text) returns bool as $$
    
    DECLARE
        
        existant_role role_view;
        stmt text;
        
    BEGIN
        select * into role_view from role_view where rolname = in_group_name;
        
        if not found then
            return 'f'::bool;
            
        else
            return 't'::bool;
        end if;            
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__create_group(in_group_name TEXT, in_dbname TEXT) RETURNS int as $$
    
    DECLARE
        
        stmt text;
        
    BEGIN
        stmt := 'create role '|| quote_ident(quote_ident(in_dbname) || '_lsmb_' || quote_ident(in_group_name) );
        execute stmt;
        return 1;
    END;
    
$$ language 'plpgsql';

CREATE OR REPLACE FUNCTION admin__delete_user(in_username TEXT) returns INT as $$
    
    DECLARE
        stmt text;
        a_user users;
    BEGIN
    
        select * into a_user from users where username = in_username;
        
        IF NOT FOUND THEN
        
            raise exception 'User not found.';
        ELSIF FOUND THEN
    
            stmt := ' drop user ' || quote_ident(a_user.username);
            execute stmt;
            
            -- also gets user_connection
            delete from users where id = a_user.id; 
            delete from entity where id = a_user.entity_id;
                                        
        END IF;   
    END;
    
$$ language 'plpgsql';

comment on function admin__delete_user(text) is $$ 
    Drops the provided user, as well as deletes the entity and user configuration data.
$$;

CREATE OR REPLACE FUNCTION admin__delete_group (in_dbname TEXT, in_group_name TEXT) returns bool as $$
    
    DECLARE
        stmt text;
        a_role role_view;
    BEGIN
        
        select * into a_role from role_view where rolname = in_group_name;
        
        if not found then
            return 'f'::bool;
        else
            stmt := 'drop role ' || quote_ident(in_dbname || '_lsmb_' || in_group_name);
            execute stmt;
            return 't'::bool;
        end if;
    END;
$$ language 'plpgsql';

comment on function admin__delete_group(text,text) IS $$ 
    Deletes the input group from the database. Not designed to be used to 
    remove a login-capable user.
$$;

CREATE OR REPLACE FUNCTION admin__list_roles(in_username text)
RETURNS SETOF text AS
$$
DECLARE out_rolename RECORD;
BEGIN
	FOR out_rolename IN 
		SELECT rolname FROM pg_authid 
		WHERE oid IN (SELECT id FROM connectby(
			'(SELECT m.member, m.roleid, r.oid FROM pg_authid r 
			LEFT JOIN pg_auth_members m ON (r.oid = m.roleid)) a',
			'oid', 'member', 'oid', '320461', '0', ','
			) c(id integer, parent integer, "level" integer, 
				path text, list_order integer)
			)
	LOOP
		RETURN NEXT out_rolename.rolname;
	END LOOP;
END;
$$ LANGUAGE PLPGSQL;

-- TODO:  Add admin user


--CREATE OR REPLACE FUNCTION admin_audit_log () returns int as $$
    
    
    
--$$ language plpgsql;

create or replace function admin__is_user (in_user text) returns bool as $$
    DECLARE
        pg_user pg_roles;
    
    BEGIN
    
        select * into pg_user from pg_roles where rolname = in_user;
        
        IF NOT FOUND THEN
            return 'f'::bool;
        END IF;
        return 't'::bool;
    
    END;
    
$$ language plpgsql;


create or replace view user_listable as 
    select 
        u.id,
        u.username,
        e.created
    from entity e
    join users u on u.entity_id = e.id;
        

create or replace function user__get_all_users () returns setof user_listable as $$
    
    select * from user_listable;
    
$$ language sql;

create or replace function admin__get_roles (in_database text) returns setof text as $$
DECLARE
    v_rol text;
BEGIN
    FOR v_rol in 
        SELECT 
            rolname
        from 
            pg_roles
        where 
            rolname ~ ('^lsmb_' || in_database)
    LOOP
        RETURN NEXT v_rol;
    END LOOP;
END;
$$ language plpgsql;


commit;