added posibility to add or exclude private presentations for Public role users
This commit is contained in:
parent
222d08fbca
commit
788342808f
@ -173,6 +173,20 @@ module.exports = class UsersDBApi {
|
|||||||
|
|
||||||
const users = await db.users.findByPk(id, { transaction });
|
const users = await db.users.findByPk(id, { transaction });
|
||||||
|
|
||||||
|
if (data?.app_role && typeof data.app_role === 'object') {
|
||||||
|
data.app_role = data.app_role.id || data.app_role.value || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data?.custom_permissions)) {
|
||||||
|
data.custom_permissions = data.custom_permissions
|
||||||
|
.map((item) => {
|
||||||
|
if (typeof item === 'string') return item;
|
||||||
|
if (item && typeof item === 'object') return item.id || item.value;
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
if (!data?.app_role) {
|
if (!data?.app_role) {
|
||||||
data.app_role = users?.app_role?.id;
|
data.app_role = users?.app_role?.id;
|
||||||
}
|
}
|
||||||
@ -329,6 +343,37 @@ module.exports = class UsersDBApi {
|
|||||||
output.app_role_permissions = output.app_role.permissions || [];
|
output.app_role_permissions = output.app_role.permissions || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const productionPresentationAccess =
|
||||||
|
await db.production_presentation_access.findAll({
|
||||||
|
where: { userId: output.id },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
association: 'project',
|
||||||
|
attributes: ['id', 'name', 'slug'],
|
||||||
|
where: { production_presentation_visibility: 'private' },
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
output.allowed_private_production_project_ids =
|
||||||
|
productionPresentationAccess
|
||||||
|
.map((row) => {
|
||||||
|
const plain =
|
||||||
|
typeof row.get === 'function' ? row.get({ plain: true }) : row;
|
||||||
|
const project = plain.project;
|
||||||
|
if (!project?.id) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: project.id,
|
||||||
|
label: `${project.name} (${project.slug})`,
|
||||||
|
name: project.name,
|
||||||
|
slug: project.slug,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,13 @@ class UsersService extends BaseUsersService {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static normalizeRoleId(value) {
|
||||||
|
if (!value) return null;
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
if (typeof value === 'object') return value.id || value.value || null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static async createProductionPresentationAccessForPublicUser({
|
static async createProductionPresentationAccessForPublicUser({
|
||||||
user,
|
user,
|
||||||
data,
|
data,
|
||||||
@ -44,9 +51,10 @@ class UsersService extends BaseUsersService {
|
|||||||
data.allowed_private_production_project_ids,
|
data.allowed_private_production_project_ids,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!selectedProjectIds.length || !data.app_role) return;
|
const roleId = this.normalizeRoleId(data.app_role);
|
||||||
|
if (!selectedProjectIds.length || !roleId) return;
|
||||||
|
|
||||||
const role = await db.roles.findByPk(data.app_role, { transaction });
|
const role = await db.roles.findByPk(roleId, { transaction });
|
||||||
if (role?.name !== 'Public') return;
|
if (role?.name !== 'Public') return;
|
||||||
|
|
||||||
const privateProjects = await db.projects.findAll({
|
const privateProjects = await db.projects.findAll({
|
||||||
@ -76,6 +84,27 @@ class UsersService extends BaseUsersService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async updateProductionPresentationAccessForPublicUser({
|
||||||
|
user,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
}) {
|
||||||
|
if (!user?.id) return;
|
||||||
|
|
||||||
|
await db.production_presentation_access.destroy({
|
||||||
|
where: { userId: user.id },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.createProductionPresentationAccessForPublicUser({
|
||||||
|
user,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user with email validation and optional invitation
|
* Create user with email validation and optional invitation
|
||||||
*/
|
*/
|
||||||
@ -135,6 +164,43 @@ class UsersService extends BaseUsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async update(data, id, currentUser) {
|
||||||
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingUser = await UsersDBApi.findBy({ id }, { transaction });
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
throw new ValidationError('UsersNotFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await UsersDBApi.update(id, data, {
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
data,
|
||||||
|
'allowed_private_production_project_ids',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await this.updateProductionPresentationAccessForPublicUser({
|
||||||
|
user,
|
||||||
|
data,
|
||||||
|
currentUser,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.commit();
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove user with self-deletion and permission checks
|
* Remove user with self-deletion and permission checks
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React, { useEffect, useId, useState } from 'react';
|
import React, { useEffect, useId, useRef, useState } from 'react';
|
||||||
import { AsyncPaginate } from 'react-select-async-paginate';
|
import { AsyncPaginate } from 'react-select-async-paginate';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const areStringArraysEqual = (left = [], right = []) =>
|
const areStringArraysEqual = (left = [], right = []) =>
|
||||||
left.length === right.length && left.every((value, index) => value === right[index]);
|
left.length === right.length &&
|
||||||
|
left.every((value, index) => value === right[index]);
|
||||||
|
|
||||||
const areSelectOptionsEqual = (left = [], right = []) =>
|
const areSelectOptionsEqual = (left = [], right = []) =>
|
||||||
left.length === right.length &&
|
left.length === right.length &&
|
||||||
@ -21,6 +22,7 @@ export const SelectFieldMany = ({
|
|||||||
showField,
|
showField,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState([]);
|
const [value, setValue] = useState([]);
|
||||||
|
const appliedOptionsSignatureRef = useRef<string | null>(null);
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -47,16 +49,26 @@ export const SelectFieldMany = ({
|
|||||||
label: el[showField],
|
label: el[showField],
|
||||||
}));
|
}));
|
||||||
const selectedIds = options.map((el) => el.id);
|
const selectedIds = options.map((el) => el.id);
|
||||||
|
const optionsSignature = selectedIds.join('|');
|
||||||
setValue((currentValue) =>
|
const shouldApplyInitialOptions =
|
||||||
areSelectOptionsEqual(currentValue, selectedOptions)
|
appliedOptionsSignatureRef.current !== optionsSignature;
|
||||||
? currentValue
|
const fieldValueMatchesInitialOptions = areStringArraysEqual(
|
||||||
: selectedOptions,
|
field.value,
|
||||||
|
selectedIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!areStringArraysEqual(field.value, selectedIds)) {
|
if (shouldApplyInitialOptions) {
|
||||||
|
appliedOptionsSignatureRef.current = optionsSignature;
|
||||||
form.setFieldValue(field.name, selectedIds, false);
|
form.setFieldValue(field.name, selectedIds, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldApplyInitialOptions || fieldValueMatchesInitialOptions) {
|
||||||
|
setValue((currentValue) =>
|
||||||
|
areSelectOptionsEqual(currentValue, selectedOptions)
|
||||||
|
? currentValue
|
||||||
|
: selectedOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [field.name, field.value, form, options, showField]);
|
}, [field.name, field.value, form, options, showField]);
|
||||||
|
|
||||||
@ -65,11 +77,12 @@ export const SelectFieldMany = ({
|
|||||||
label: data.label,
|
label: data.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (data: Array<{ value: string; label: string }>) => {
|
const handleChange = (data: Array<{ value: string; label: string }> | null) => {
|
||||||
setValue(data);
|
const selectedOptions = data || [];
|
||||||
|
setValue(selectedOptions);
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
field.name,
|
field.name,
|
||||||
data.map((el) => el?.value || null),
|
selectedOptions.map((el) => el?.value || null),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,10 @@ const initVals = {
|
|||||||
avatar: [] as Array<{ id: string; publicUrl: string }>,
|
avatar: [] as Array<{ id: string; publicUrl: string }>,
|
||||||
app_role: null as { id: string; name: string } | null,
|
app_role: null as { id: string; name: string } | null,
|
||||||
custom_permissions: [] as Array<{ id: string; name: string }>,
|
custom_permissions: [] as Array<{ id: string; name: string }>,
|
||||||
|
allowed_private_production_project_ids: [] as Array<{
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}>,
|
||||||
password: '',
|
password: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,10 +48,20 @@ const EditUsersPage = () => {
|
|||||||
fetchAction: fetch,
|
fetchAction: fetch,
|
||||||
initialValues: initVals,
|
initialValues: initVals,
|
||||||
});
|
});
|
||||||
|
const [selectedRoleLabel, setSelectedRoleLabel] = React.useState('');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setSelectedRoleLabel(initialValues.app_role?.name || '');
|
||||||
|
}, [initialValues.app_role?.name]);
|
||||||
|
|
||||||
const handleSubmit = async (data: typeof initVals) => {
|
const handleSubmit = async (data: typeof initVals) => {
|
||||||
if (id) {
|
if (id) {
|
||||||
await dispatch(update({ id, data }));
|
const payload = { ...data };
|
||||||
|
if (selectedRoleLabel !== 'Public') {
|
||||||
|
payload.allowed_private_production_project_ids = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await dispatch(update({ id, data: payload }));
|
||||||
await router.push('/users/users-list');
|
await router.push('/users/users-list');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -71,85 +85,119 @@ const EditUsersPage = () => {
|
|||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={(values) => handleSubmit(values)}
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
>
|
>
|
||||||
<Form>
|
{({ setFieldValue }) => (
|
||||||
<FormField label='First Name'>
|
<Form>
|
||||||
<Field name='firstName' placeholder='First Name' />
|
<FormField label='First Name'>
|
||||||
</FormField>
|
<Field name='firstName' placeholder='First Name' />
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<FormField label='Last Name'>
|
<FormField label='Last Name'>
|
||||||
<Field name='lastName' placeholder='Last Name' />
|
<Field name='lastName' placeholder='Last Name' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField label='Phone Number'>
|
<FormField label='Phone Number'>
|
||||||
<Field name='phoneNumber' placeholder='Phone Number' />
|
<Field name='phoneNumber' placeholder='Phone Number' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField label='E-Mail'>
|
<FormField label='E-Mail'>
|
||||||
<Field name='email' placeholder='E-Mail' />
|
<Field name='email' placeholder='E-Mail' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField label='Disabled' labelFor='disabled'>
|
<FormField label='Disabled' labelFor='disabled'>
|
||||||
<Field name='disabled' id='disabled' component={SwitchField} />
|
<Field
|
||||||
</FormField>
|
name='disabled'
|
||||||
|
id='disabled'
|
||||||
|
component={SwitchField}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<FormField>
|
<FormField>
|
||||||
<Field
|
<Field
|
||||||
label='Avatar'
|
label='Avatar'
|
||||||
color='info'
|
color='info'
|
||||||
icon={mdiUpload}
|
icon={mdiUpload}
|
||||||
path={'users/avatar'}
|
path={'users/avatar'}
|
||||||
name='avatar'
|
name='avatar'
|
||||||
id='avatar'
|
id='avatar'
|
||||||
schema={{
|
schema={{
|
||||||
size: undefined,
|
size: undefined,
|
||||||
formats: undefined,
|
formats: undefined,
|
||||||
}}
|
}}
|
||||||
component={FormImagePicker}
|
component={FormImagePicker}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField label='App Role' labelFor='app_role'>
|
<FormField label='App Role' labelFor='app_role'>
|
||||||
<Field
|
<Field
|
||||||
name='app_role'
|
name='app_role'
|
||||||
id='app_role'
|
id='app_role'
|
||||||
component={SelectField}
|
component={SelectField}
|
||||||
options={initialValues.app_role}
|
options={initialValues.app_role}
|
||||||
itemRef={'roles'}
|
itemRef={'roles'}
|
||||||
showField={'name'}
|
showField={'name'}
|
||||||
/>
|
onOptionChange={(option) => {
|
||||||
</FormField>
|
const label = option?.label || '';
|
||||||
|
setSelectedRoleLabel(label);
|
||||||
|
if (label !== 'Public') {
|
||||||
|
setFieldValue(
|
||||||
|
'allowed_private_production_project_ids',
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<FormField
|
{selectedRoleLabel === 'Public' && (
|
||||||
label='Custom Permissions'
|
<FormField
|
||||||
labelFor='custom_permissions'
|
label='Allowed Private Production Presentations'
|
||||||
>
|
labelFor='allowed_private_production_project_ids'
|
||||||
<Field
|
>
|
||||||
name='custom_permissions'
|
<Field
|
||||||
id='custom_permissions'
|
name='allowed_private_production_project_ids'
|
||||||
component={SelectFieldMany}
|
id='allowed_private_production_project_ids'
|
||||||
options={initialValues.custom_permissions}
|
component={SelectFieldMany}
|
||||||
itemRef={'permissions'}
|
options={
|
||||||
showField={'name'}
|
initialValues.allowed_private_production_project_ids
|
||||||
/>
|
}
|
||||||
</FormField>
|
itemRef='runtime-access/private-production-presentations'
|
||||||
|
showField='label'
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField label='Password'>
|
<FormField
|
||||||
<Field name='password' placeholder='password' />
|
label='Custom Permissions'
|
||||||
</FormField>
|
labelFor='custom_permissions'
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
name='custom_permissions'
|
||||||
|
id='custom_permissions'
|
||||||
|
component={SelectFieldMany}
|
||||||
|
options={initialValues.custom_permissions}
|
||||||
|
itemRef={'permissions'}
|
||||||
|
showField={'name'}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<BaseDivider />
|
<FormField label='Password'>
|
||||||
<BaseButtons>
|
<Field name='password' placeholder='password' />
|
||||||
<BaseButton type='submit' color='info' label='Submit' />
|
</FormField>
|
||||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
|
||||||
<BaseButton
|
<BaseDivider />
|
||||||
type='reset'
|
<BaseButtons>
|
||||||
color='danger'
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
outline
|
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||||
label='Cancel'
|
<BaseButton
|
||||||
onClick={() => router.push('/users/users-list')}
|
type='reset'
|
||||||
/>
|
color='danger'
|
||||||
</BaseButtons>
|
outline
|
||||||
</Form>
|
label='Cancel'
|
||||||
|
onClick={() => router.push('/users/users-list')}
|
||||||
|
/>
|
||||||
|
</BaseButtons>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
|
|||||||
@ -25,7 +25,9 @@ export interface User extends BaseEntity {
|
|||||||
app_role?: Role | null;
|
app_role?: Role | null;
|
||||||
custom_permissions?: PermissionEntity[];
|
custom_permissions?: PermissionEntity[];
|
||||||
allowedPrivateProductionSlugs?: string[];
|
allowedPrivateProductionSlugs?: string[];
|
||||||
allowed_private_production_project_ids?: string[];
|
allowed_private_production_project_ids?: Array<
|
||||||
|
string | { id: string; label?: string; name?: string; slug?: string }
|
||||||
|
>;
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user