Skip to content

Commit

Permalink
Merge pull request #429 from bcgov/feat/toggle-admin-status
Browse files Browse the repository at this point in the history
Feat/toggle admin status
  • Loading branch information
IanFonzie authored Nov 7, 2023
2 parents 01b41fd + 4ffcf65 commit bc8ba4d
Show file tree
Hide file tree
Showing 14 changed files with 536 additions and 101 deletions.
68 changes: 53 additions & 15 deletions src/back-end/lib/db/affiliation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,32 @@ export const approveAffiliation = tryDb<[Id], Affiliation>(
}
);

export const updateAdminStatus = tryDb<[Id, MembershipType], Affiliation>(
async (connection, id, membershipType: MembershipType) => {
const now = new Date();
const [result] = await connection<RawAffiliation>("affiliations")
.update(
{
membershipType,
updatedAt: now
} as RawAffiliation,
"*"
)
.where({
id
})
.whereIn("organization", function () {
this.select("id").from("organizations").where({
active: true
});
});
if (!result) {
throw new Error("unable to update admin status");
}
return valid(await rawAffiliationToAffiliation(connection, result));
}
);

export const deleteAffiliation = tryDb<[Id], Affiliation>(
async (connection, id) => {
const now = new Date();
Expand All @@ -274,24 +300,36 @@ export const deleteAffiliation = tryDb<[Id], Affiliation>(
}
);

export async function isUserOwnerOfOrg(
function makeIsUserTypeChecker(
membershipType: MembershipType
): (connection: Connection, user: User, ordId: Id) => Promise<boolean> {
return async (connection: Connection, user: User, orgId: Id) => {
if (!user) {
return false;
}
const result = await connection<RawAffiliation>("affiliations")
.where({
user: user.id,
organization: orgId,
membershipType,
membershipStatus: MembershipStatus.Active
})
.first();

return !!result;
};
}
const isUserAdminOfOrg = makeIsUserTypeChecker(MembershipType.Admin);

export const isUserOwnerOfOrg = makeIsUserTypeChecker(MembershipType.Owner);

export const isUserOwnerOrAdminOfOrg = async (
connection: Connection,
user: User,
orgId: Id
): Promise<boolean> {
if (!user) {
return false;
}
const result = await connection<RawAffiliation>("affiliations")
.where({
user: user.id,
organization: orgId,
membershipType: MembershipType.Owner
})
.first();

return !!result;
}
) =>
(await isUserOwnerOfOrg(connection, user, orgId)) ||
(await isUserAdminOfOrg(connection, user, orgId));

export async function readActiveOwnerCount(
connection: Connection,
Expand Down
48 changes: 31 additions & 17 deletions src/back-end/lib/db/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ async function rawOrganizationSlimToOrganizationSlim(
possessOneServiceArea,
numTeamMembers,
active,
serviceAreas
serviceAreas,
viewerIsOrgAdmin
} = raw;
let fetchedLogoImageFile: FileRecord | undefined;
if (logoImageFile) {
Expand All @@ -130,7 +131,8 @@ async function rawOrganizationSlimToOrganizationSlim(
active,
numTeamMembers:
numTeamMembers === undefined ? undefined : parseInt(numTeamMembers, 10),
serviceAreas
serviceAreas,
...(viewerIsOrgAdmin ? { viewerIsOrgAdmin } : {})
};
}

Expand Down Expand Up @@ -180,18 +182,7 @@ function generateOrganizationQuery(connection: Connection) {
})
.as("numTeamMembers"),
connection.raw(
`(
SELECT
coalesce(
json_agg(sa),
'[]' :: json
) AS "serviceAreas"
FROM
"twuOrganizationServiceAreas" tosa
JOIN "serviceAreas" sa ON tosa."serviceArea" = sa.id
WHERE
tosa.organization = ?
)`,
'(select coalesce(json_agg(sa), \'[]\' :: json) as "serviceAreas" from "twuOrganizationServiceAreas" tosa join "serviceAreas" sa on tosa."serviceArea" = sa.id where tosa.organization = ?)',
connection.ref("organizations.id")
)
);
Expand Down Expand Up @@ -333,6 +324,24 @@ export const readManyOrganizations = tryDb<
query = query.andWhere({ "organizations.active": true });
}

// Used to render links for viewing organizations.
if (session) {
query = query.select(
connection.raw('exists(?) as "viewerIsOrgAdmin"', [
connection
.select("user")
.from("affiliations")
.where({
organization: connection.ref("organizations.id"),
membershipStatus: MembershipStatus.Active,
membershipType: MembershipType.Admin,
user: session.user.id
})
.first()
])
);
}

// Default is to only have one page because we are requesting everything.
let numPages = 1;

Expand Down Expand Up @@ -369,9 +378,13 @@ export const readManyOrganizations = tryDb<
acceptedTWUTerms,
acceptedSWUTerms,
active,
serviceAreas
serviceAreas,
viewerIsOrgAdmin
} = raw;
if (!isAdmin(session) && raw.owner !== session?.user.id) {
if (
!isAdmin(session) &&
!(raw.owner === session?.user.id || viewerIsOrgAdmin)
) {
return await rawOrganizationSlimToOrganizationSlim(connection, {
id,
legalName,
Expand All @@ -394,7 +407,8 @@ export const readManyOrganizations = tryDb<
possessOneServiceArea: serviceAreas.length > 0,
acceptedTWUTerms,
acceptedSWUTerms,
serviceAreas
serviceAreas,
viewerIsOrgAdmin
});
}
})
Expand Down
27 changes: 21 additions & 6 deletions src/back-end/lib/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isTWUOpportunityAuthor,
isTWUProposalAuthor,
isUserOwnerOfOrg,
isUserOwnerOrAdminOfOrg,
userHasAcceptedCurrentTerms,
userHasAcceptedPreviousTerms
} from "back-end/lib/db";
Expand Down Expand Up @@ -171,7 +172,7 @@ export async function readOneOrganization(
}
return (
isAdmin(session) ||
(await isUserOwnerOfOrg(connection, session.user, orgId))
(await isUserOwnerOrAdminOfOrg(connection, session.user, orgId))
);
}

Expand Down Expand Up @@ -223,7 +224,8 @@ export async function readManyAffiliationsForOrganization(
// Membership lists for organizations can only be read by admins or organization owner
return (
isAdmin(session) ||
(!!session && (await isUserOwnerOfOrg(connection, session.user, orgId)))
(!!session &&
(await isUserOwnerOrAdminOfOrg(connection, session.user, orgId)))
);
}

Expand All @@ -235,11 +237,12 @@ export async function createAffiliation(
// New affiliations can be created only by organization owners, or admins
return (
isAdmin(session) ||
(!!session && (await isUserOwnerOfOrg(connection, session.user, orgId)))
(!!session &&
(await isUserOwnerOrAdminOfOrg(connection, session.user, orgId)))
);
}

export function updateAffiliation(
export function approveAffiliation(
session: Session,
affiliation: Affiliation
): boolean {
Expand All @@ -249,17 +252,29 @@ export function updateAffiliation(
);
}

export async function updateAffiliationAdminStatus(
connection: Connection,
session: Session,
orgId: string
): Promise<boolean> {
return (
isAdmin(session) ||
(!!session &&
(await isUserOwnerOrAdminOfOrg(connection, session.user, orgId)))
);
}

export async function deleteAffiliation(
connection: Connection,
session: Session,
affiliation: Affiliation
): Promise<boolean> {
// Affiliations can be deleted by the user who owns them, an owner of the org, or an admin
// Affiliations can be deleted by the user who owns them, an owner/admin of the org, or an admin
return (
isAdmin(session) ||
(!!session && isOwnAccount(session, affiliation.user.id)) ||
(!!session &&
(await isUserOwnerOfOrg(
(await isUserOwnerOrAdminOfOrg(
connection,
session.user,
affiliation.organization.id
Expand Down
Loading

0 comments on commit bc8ba4d

Please sign in to comment.