Repository: directus/directus
File: api/src/services/roles.ts (TypeScript)

Code before:

		return super.createMany(data, opts);
	}

	private async checkUpdatedUserLimits(roleId: PrimaryKey, data: Partial<Item>) {
		const increasedCounts: AccessTypeCount = {
			admin: 0,
			app: 0,
			api: 0,
		};

		const existingRole:
			| {
					count: number | string;
					admin_access: number | boolean | null;
					app_access: number | boolean | null;
			  }
			| undefined = await this.knex
			.count('directus_users.id', { as: 'count' })
			.select('directus_roles.admin_access', 'directus_roles.app_access')
			.from('directus_users')
			.where('directus_roles.id', '=', roleId)
			.andWhere('directus_users.status', '=', 'active')
			.leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
			.groupBy('directus_roles.admin_access', 'directus_roles.app_access')
			.first();

		if (!existingRole) return;

		let increasedUsers = 0;
		const existingIds: PrimaryKey[] = [];

		if ('users' in data) {
			await this.checkForOtherAdminUsers(roleId, data['users']);

			const users: Alterations<User, 'id'> | (string | Partial<User>)[] = data['users'];

			if (Array.isArray(users)) {
				increasedUsers = users.length - Number(existingRole.count);

				for (const user of users) {
					if (typeof user === 'string') {
						existingIds.push(user);
					} else if (typeof user === 'object' && 'id' in user) {
						existingIds.push(user['id']);
					}
				}
			} else {
				increasedUsers += users.create.length;
				increasedUsers -= users.delete.length;


Review comment (suggestion):
As mentioned in https://github.com/directus/directus/pull/22479#discussion_r1625781412, this query returns `undefined` for roles that previously had no users assigned.
Now it's no longer blocking the update, but the increased user count is not checked.

Code after:
					}
				}
			}
		}

		await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);

		return super.createMany(data, opts);
	}

	override async updateOne(key: PrimaryKey, data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
		this.assertValidIpAccess(data);

		try {
			const increasedCounts: AccessTypeCount = {
				admin: 0,
				app: 0,
				api: 0,
			};

			let increasedUsers = 0;

			const existingIds: PrimaryKey[] = [];

			let existingRole: RoleCount | undefined = await this.knex
				.count('directus_users.id', { as: 'count' })
				.select('directus_roles.admin_access', 'directus_roles.app_access')
				.from('directus_users')
				.where('directus_roles.id', '=', key)
				.andWhere('directus_users.status', '=', 'active')
				.leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
				.groupBy('directus_roles.admin_access', 'directus_roles.app_access')
				.first();

			if (!existingRole) {
				try {
					const role = (await this.knex
						.select('admin_access', 'app_access')
						.from('directus_roles')
						.where('id', '=', key)
						.first()) ?? { admin_access: null, app_access: null };

					existingRole = { count: 0, ...role } as RoleCount;
				} catch {
					existingRole = { count: 0, admin_access: null, app_access: null } as RoleCount;
				}
			}

			if ('users' in data) {
				await this.checkForOtherAdminUsers(key, data['users']);