diff --git a/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationProductDefinitions.ts b/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationProductDefinitions.ts new file mode 100644 index 000000000..42bcb5abb --- /dev/null +++ b/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationProductDefinitions.ts @@ -0,0 +1,36 @@ +import prisma from '../prisma.js'; + +export async function updateOrganizationProductDefinitions( + orgId: number, + productDefinitions: number[] +) { + const old = ( + await prisma.organizationProductDefinitions.findMany({ + where: { + OrganizationId: orgId + } + }) + ).map((x) => x.ProductDefinitionId); + const newEntries = productDefinitions.filter( + (productDefinition) => !old.includes(productDefinition) + ); + const removeEntries = old.filter( + (productDefinition) => !productDefinitions.includes(productDefinition) + ); + await prisma.$transaction([ + prisma.organizationProductDefinitions.deleteMany({ + where: { + OrganizationId: orgId, + ProductDefinitionId: { + in: removeEntries + } + } + }), + prisma.organizationProductDefinitions.createMany({ + data: newEntries.map((store) => ({ + OrganizationId: orgId, + ProductDefinitionId: store + })) + }) + ]); +} diff --git a/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationStores.ts b/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationStores.ts new file mode 100644 index 000000000..700810642 --- /dev/null +++ b/source/SIL.AppBuilder.Portal/common/databaseProxy/OrganizationStores.ts @@ -0,0 +1,29 @@ +import prisma from '../prisma.js'; + +export async function updateOrganizationStores(orgId: number, stores: number[]) { + const old = ( + await prisma.organizationStores.findMany({ + where: { + OrganizationId: orgId + } + }) + ).map((x) => x.StoreId); + const newEntries = stores.filter((store) => !old.includes(store)); + const removeEntries = old.filter((store) => !stores.includes(store)); + await prisma.$transaction([ + prisma.organizationStores.deleteMany({ + where: { + OrganizationId: orgId, + StoreId: { + in: removeEntries + } + } + }), + prisma.organizationStores.createMany({ + data: newEntries.map((store) => ({ + OrganizationId: orgId, + StoreId: store + })) + }) + ]); +} diff --git a/source/SIL.AppBuilder.Portal/common/databaseProxy/UserRoles.ts b/source/SIL.AppBuilder.Portal/common/databaseProxy/UserRoles.ts new file mode 100644 index 000000000..9f45cc534 --- /dev/null +++ b/source/SIL.AppBuilder.Portal/common/databaseProxy/UserRoles.ts @@ -0,0 +1,37 @@ +import prisma from '../prisma.js'; +import { RoleId } from '../public/prisma.js'; + +export async function setUserRolesForOrganization( + userId: number, + organizationId: number, + roles: RoleId[] +) { + const old = ( + await prisma.userRoles.findMany({ + where: { + UserId: userId, + OrganizationId: organizationId + } + }) + ).map((r) => r.RoleId); + const remove = old.filter((role) => !roles.includes(role)); + const add = roles.filter((role) => !old.includes(role)); + await prisma.$transaction([ + prisma.userRoles.deleteMany({ + where: { + UserId: userId, + OrganizationId: organizationId, + RoleId: { + in: remove + } + } + }), + prisma.userRoles.createMany({ + data: add.map((r) => ({ + UserId: userId, + OrganizationId: organizationId, + RoleId: r + })) + }) + ]); +} diff --git a/source/SIL.AppBuilder.Portal/common/databaseProxy/index.ts b/source/SIL.AppBuilder.Portal/common/databaseProxy/index.ts index 0e215797a..3b1f9598d 100644 --- a/source/SIL.AppBuilder.Portal/common/databaseProxy/index.ts +++ b/source/SIL.AppBuilder.Portal/common/databaseProxy/index.ts @@ -4,8 +4,11 @@ import { WRITE_METHODS } from '../ReadonlyPrisma.js'; import prisma from '../prisma.js'; import * as groupMemberships from './GroupMemberships.js'; import * as groups from './Groups.js'; +import * as organizationProductDefinitions from './OrganizationProductDefinitions.js'; +import * as organizationStores from './OrganizationStores.js'; import * as products from './Products.js'; import * as projects from './Projects.js'; +import * as userRoles from './UserRoles.js'; import * as utility from './utility.js'; type RecurseRemove = { @@ -33,6 +36,9 @@ const handlers = { projects, groups, groupMemberships, + organizationStores, + organizationProductDefinitions, + userRoles, utility }; // @ts-expect-error this is in fact immediately populated diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/edit/+page.server.ts b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/edit/+page.server.ts index a7a16191a..f85b4cc7e 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/edit/+page.server.ts +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/admin/settings/organizations/edit/+page.server.ts @@ -94,19 +94,14 @@ export const actions = { LogoUrl: logoURL, OwnerId: owner, PublicByDefault: publicByDefault, - WebsiteUrl: websiteURL, - OrganizationStores: { - deleteMany: {}, - create: stores - .filter((s) => s.enabled) - .map((s) => ({ - Store: { - connect: { Id: s.storeId } - } - })) - } + WebsiteUrl: websiteURL } }); + await DatabaseWrites.organizationStores.updateOrganizationStores( + id, + stores.filter((s) => s.enabled).map((s) => s.storeId) + ); + return { ok: true, form }; } catch (e) { if (e instanceof v.ValiError) return { form, ok: false, errors: e.issues }; diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/products/+page.server.ts b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/products/+page.server.ts index 67136bc65..35761f701 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/products/+page.server.ts +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/products/+page.server.ts @@ -53,24 +53,16 @@ export const actions = { if (!form.valid) return fail(400, { form, ok: false, errors: form.errors }); try { const { id, publicByDefault, products } = form.data; + await DatabaseWrites.organizationProductDefinitions.updateOrganizationProductDefinitions( + id, + products.filter((p) => p.enabled).map((p) => p.productId) + ); await DatabaseWrites.organizations.update({ where: { Id: id }, data: { - PublicByDefault: publicByDefault, - OrganizationProductDefinitions: { - deleteMany: {}, - create: products - .filter((p) => p.enabled) - .map((p) => ({ - ProductDefinition: { - connect: { - Id: p.productId - } - } - })) - } + PublicByDefault: publicByDefault } }); return { ok: true, form }; diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/stores/+page.server.ts b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/stores/+page.server.ts index d38908341..020334752 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/stores/+page.server.ts +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/organizations/[id]/settings/stores/+page.server.ts @@ -51,25 +51,10 @@ export const actions = { if (!form.valid) return fail(400, { form, ok: false, errors: form.errors }); try { const { id, stores } = form.data; - await DatabaseWrites.organizations.update({ - where: { - Id: id - }, - data: { - OrganizationStores: { - deleteMany: {}, - create: stores - .filter((s) => s.enabled) - .map((s) => ({ - Store: { - connect: { - Id: s.storeId - } - } - })) - } - } - }); + await DatabaseWrites.organizationStores.updateOrganizationStores( + id, + stores.filter((s) => s.enabled).map((s) => s.storeId) + ); return { ok: true, form }; } catch (e) { if (e instanceof v.ValiError) return { form, ok: false, errors: e.issues }; diff --git a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/users/[id=idNumber]/settings/roles/+page.server.ts b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/users/[id=idNumber]/settings/roles/+page.server.ts index af95fe74f..9b00de8b4 100644 --- a/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/users/[id=idNumber]/settings/roles/+page.server.ts +++ b/source/SIL.AppBuilder.Portal/src/routes/(authenticated)/users/[id=idNumber]/settings/roles/+page.server.ts @@ -79,38 +79,13 @@ export const actions = { const superAdmin = adminRoles.find((r) => r.RoleId === RoleId.SuperAdmin); // Filter for legal orgs to modify, then map to relevant table entries - const newRelationEntries = form.data.organizations - .filter((org) => superAdmin || adminRoles.find((r) => r.OrganizationId === org.id)) - .flatMap((org) => org.roles.map((role) => ({ org: org.id, role }))); - await DatabaseWrites.users.update({ - where: { - Id: parseInt(event.params.id) - }, - data: { - UserRoles: { - // Delete all existing role connections that the active user has permission to edit - // If superadmin, that's everything, otherwise, whatever orgs the active user is orgAdmin in - // Also, never delete superadmin roles - deleteMany: { - RoleId: { - not: RoleId.SuperAdmin - }, - AND: superAdmin - ? undefined - : { - OrganizationId: { - in: adminRoles.map((r) => r.OrganizationId) - } - } - }, - // Finally, add the new entries. This must be filtered for orgs already - create: newRelationEntries.map((re) => ({ - OrganizationId: re.org, - RoleId: re.role - })) - } - } - }); + const newRelationEntries = form.data.organizations.filter( + (org) => superAdmin || adminRoles.find((r) => r.OrganizationId === org.id) + ); + const subjectUserId = parseInt(event.params.id); + for (const org of newRelationEntries) { + await DatabaseWrites.userRoles.setUserRolesForOrganization(subjectUserId, org.id, org.roles); + } return { form, ok: true }; } } satisfies Actions;