fix(workspace-invite): inconsistence in roles names
This commit is contained in:
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
public static class KnownRoles
|
public static class KnownRoles
|
||||||
{
|
{
|
||||||
public const string Administrator = nameof(Administrator);
|
public const string Administrator = "administrator";
|
||||||
public const string Manager = nameof(Manager);
|
public const string Manager = "manager";
|
||||||
public const string Client = nameof(Client);
|
public const string Client = "client";
|
||||||
public const string Provider = nameof(Provider);
|
public const string Provider = "provider";
|
||||||
public const string WorkspaceMember = nameof(WorkspaceMember);
|
public const string WorkspaceMember = "workspace-member";
|
||||||
public const string Developer = nameof(Developer);
|
public const string Developer = "developer";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Socialize.Api.Modules.Workspaces.Data;
|
||||||
|
|
||||||
|
public static class WorkspaceInviteStatuses
|
||||||
|
{
|
||||||
|
public const string Pending = "Pending";
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public class CreateWorkspaceInviteRequestValidator
|
|||||||
public CreateWorkspaceInviteRequestValidator()
|
public CreateWorkspaceInviteRequestValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x.Email).NotEmpty().MaximumLength(256).EmailAddress();
|
RuleFor(x => x.Email).NotEmpty().MaximumLength(256).EmailAddress();
|
||||||
RuleFor(x => x.Role).NotEmpty().Must(role => AllowedRoles.Contains(role));
|
RuleFor(x => x.Role).NotEmpty().Must(role => AllowedRoles.Contains(role)).WithMessage("A valid role should be specified");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ public class CreateWorkspaceInviteHandler(
|
|||||||
bool duplicateInvite = await dbContext.WorkspaceInvites.AnyAsync(
|
bool duplicateInvite = await dbContext.WorkspaceInvites.AnyAsync(
|
||||||
invite => invite.WorkspaceId == workspaceId &&
|
invite => invite.WorkspaceId == workspaceId &&
|
||||||
invite.Email == normalizedEmail &&
|
invite.Email == normalizedEmail &&
|
||||||
invite.Status == "Pending",
|
invite.Status == WorkspaceInviteStatuses.Pending,
|
||||||
ct);
|
ct);
|
||||||
|
|
||||||
if (duplicateInvite)
|
if (duplicateInvite)
|
||||||
@@ -81,7 +81,7 @@ public class CreateWorkspaceInviteHandler(
|
|||||||
WorkspaceId = workspaceId,
|
WorkspaceId = workspaceId,
|
||||||
Email = normalizedEmail,
|
Email = normalizedEmail,
|
||||||
Role = normalizedRole,
|
Role = normalizedRole,
|
||||||
Status = "Pending",
|
Status = WorkspaceInviteStatuses.Pending,
|
||||||
InvitedByUserId = User.GetUserId(),
|
InvitedByUserId = User.GetUserId(),
|
||||||
CreatedAt = DateTimeOffset.UtcNow,
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
const inviteForm = reactive({
|
const inviteForm = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
role: 'workspaceMember',
|
role: 'workspace-member',
|
||||||
});
|
});
|
||||||
|
|
||||||
const pendingInvites = computed(() =>
|
const pendingInvites = computed(() =>
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
inviteForm.email = '';
|
inviteForm.email = '';
|
||||||
inviteForm.role = 'workspaceMember';
|
inviteForm.role = 'workspace-member';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to invite workspace member:', error);
|
console.error('Failed to invite workspace member:', error);
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@
|
|||||||
<label class="field">
|
<label class="field">
|
||||||
<span>{{ t('workspaceSettings.fields.memberRole') }}</span>
|
<span>{{ t('workspaceSettings.fields.memberRole') }}</span>
|
||||||
<select v-model="inviteForm.role">
|
<select v-model="inviteForm.role">
|
||||||
<option value="workspaceMember">{{ t('workspaceSettings.roles.workspaceMember') }}</option>
|
<option value="workspace-member">{{ t('workspaceSettings.roles.workspace-member') }}</option>
|
||||||
<option value="client">{{ t('workspaceSettings.roles.client') }}</option>
|
<option value="client">{{ t('workspaceSettings.roles.client') }}</option>
|
||||||
<option value="provider">{{ t('workspaceSettings.roles.provider') }}</option>
|
<option value="provider">{{ t('workspaceSettings.roles.provider') }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -512,7 +512,7 @@
|
|||||||
"manager": "Manager",
|
"manager": "Manager",
|
||||||
"client": "Client reviewer",
|
"client": "Client reviewer",
|
||||||
"provider": "Subcontractor",
|
"provider": "Subcontractor",
|
||||||
"workspaceMember": "Workspace member"
|
"workspace-member": "Workspace member"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -638,4 +638,4 @@
|
|||||||
"imageLoad": "Error loading image",
|
"imageLoad": "Error loading image",
|
||||||
"imageUpload": "Error uploading image"
|
"imageUpload": "Error uploading image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -512,7 +512,7 @@
|
|||||||
"manager": "Gestionnaire",
|
"manager": "Gestionnaire",
|
||||||
"client": "Réviseur client",
|
"client": "Réviseur client",
|
||||||
"provider": "Sous-traitant",
|
"provider": "Sous-traitant",
|
||||||
"workspaceMember": "Membre de l'espace"
|
"workspace-member": "Membre de l'espace"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
@@ -638,4 +638,4 @@
|
|||||||
"imageLoad": "Erreur lors du chargement de l'image",
|
"imageLoad": "Erreur lors du chargement de l'image",
|
||||||
"imageUpload": "Erreur lors du téléchargement de l'image"
|
"imageUpload": "Erreur lors du téléchargement de l'image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,211 +27,211 @@ const DeveloperFeedbackListView = () => import('@/features/feedback/views/Develo
|
|||||||
const DeveloperFeedbackDetailView = () => import('@/features/feedback/views/DeveloperFeedbackDetailView.vue');
|
const DeveloperFeedbackDetailView = () => import('@/features/feedback/views/DeveloperFeedbackDetailView.vue');
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'landing',
|
name: 'landing',
|
||||||
component: Landing,
|
component: Landing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app',
|
path: '/app',
|
||||||
redirect: { name: 'dashboard' },
|
redirect: { name: 'dashboard' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/dashboard',
|
path: '/app/dashboard',
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
component: OverviewView,
|
component: OverviewView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/workspace',
|
path: '/app/workspace',
|
||||||
name: 'workspace-dashboard',
|
name: 'workspace-dashboard',
|
||||||
component: DashboardView,
|
component: DashboardView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/channels',
|
path: '/app/channels',
|
||||||
name: 'channels',
|
name: 'channels',
|
||||||
component: ChannelsView,
|
component: ChannelsView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/media-library',
|
path: '/app/media-library',
|
||||||
name: 'media-library',
|
name: 'media-library',
|
||||||
component: MediaLibraryView,
|
component: MediaLibraryView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/campaigns',
|
path: '/app/campaigns',
|
||||||
name: 'campaigns',
|
name: 'campaigns',
|
||||||
component: CampaignsView,
|
component: CampaignsView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/campaigns/:projectId',
|
path: '/app/campaigns/:projectId',
|
||||||
name: 'campaign-detail',
|
name: 'campaign-detail',
|
||||||
component: CampaignDetailView,
|
component: CampaignDetailView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/reviews',
|
path: '/app/reviews',
|
||||||
name: 'review-queue',
|
name: 'review-queue',
|
||||||
component: ReviewQueueView,
|
component: ReviewQueueView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/my-feedback',
|
path: '/app/my-feedback',
|
||||||
name: 'my-feedback',
|
name: 'my-feedback',
|
||||||
component: MyFeedbackListView,
|
component: MyFeedbackListView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/my-feedback/:id',
|
path: '/app/my-feedback/:id',
|
||||||
name: 'my-feedback-detail',
|
name: 'my-feedback-detail',
|
||||||
component: MyFeedbackDetailView,
|
component: MyFeedbackDetailView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/feedback',
|
path: '/app/feedback',
|
||||||
name: 'developer-feedback',
|
name: 'developer-feedback',
|
||||||
component: DeveloperFeedbackListView,
|
component: DeveloperFeedbackListView,
|
||||||
meta: { requiresAuth: true, roles: ['Developer'] },
|
meta: { requiresAuth: true, roles: ['developer'] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/feedback/:id',
|
path: '/app/feedback/:id',
|
||||||
name: 'developer-feedback-detail',
|
name: 'developer-feedback-detail',
|
||||||
component: DeveloperFeedbackDetailView,
|
component: DeveloperFeedbackDetailView,
|
||||||
meta: { requiresAuth: true, roles: ['Developer'] },
|
meta: { requiresAuth: true, roles: ['developer'] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/workspace-settings',
|
path: '/app/workspace-settings',
|
||||||
name: 'workspace-settings',
|
name: 'workspace-settings',
|
||||||
|
component: WorkspaceSettingsView,
|
||||||
|
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/app/workspaces/new',
|
||||||
|
name: 'workspace-create',
|
||||||
|
component: WorkspaceCreateView,
|
||||||
|
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/app/settings',
|
||||||
|
component: SettingsLayoutView,
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirect: { name: 'settings-user-information' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user-information',
|
||||||
|
name: 'settings-user-information',
|
||||||
|
component: UserSettingsView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'workspaces',
|
||||||
|
name: 'settings-workspaces',
|
||||||
component: WorkspaceSettingsView,
|
component: WorkspaceSettingsView,
|
||||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/app/workspaces/new',
|
path: 'integrations',
|
||||||
name: 'workspace-create',
|
name: 'settings-integrations',
|
||||||
component: WorkspaceCreateView,
|
component: IntegrationsSettingsView,
|
||||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
path: '/app/settings',
|
},
|
||||||
component: SettingsLayoutView,
|
{
|
||||||
meta: { requiresAuth: true },
|
path: '/app/content',
|
||||||
children: [
|
name: 'content-items',
|
||||||
{
|
component: ContentItemsView,
|
||||||
path: '',
|
meta: { requiresAuth: true },
|
||||||
redirect: { name: 'settings-user-information' },
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/app/content/new',
|
||||||
path: 'user-information',
|
name: 'content-item-create',
|
||||||
name: 'settings-user-information',
|
component: ContentItemDetailView,
|
||||||
component: UserSettingsView,
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'workspaces',
|
path: '/app/content/:id',
|
||||||
name: 'settings-workspaces',
|
name: 'content-item-detail',
|
||||||
component: WorkspaceSettingsView,
|
component: ContentItemDetailView,
|
||||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'integrations',
|
path: '/login',
|
||||||
name: 'settings-integrations',
|
name: 'login',
|
||||||
component: IntegrationsSettingsView,
|
component: LoginView,
|
||||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
meta: { notAuthenticated: true },
|
||||||
},
|
props: route => ({ returnUrl: route.query.returnUrl || '/app/dashboard' }),
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/profile',
|
||||||
path: '/app/content',
|
redirect: { name: 'dashboard' },
|
||||||
name: 'content-items',
|
},
|
||||||
component: ContentItemsView,
|
{
|
||||||
meta: { requiresAuth: true },
|
path: '/register',
|
||||||
},
|
name: 'register',
|
||||||
{
|
component: RegisterView,
|
||||||
path: '/app/content/new',
|
meta: { requiresAuth: false },
|
||||||
name: 'content-item-create',
|
},
|
||||||
component: ContentItemDetailView,
|
{
|
||||||
meta: { requiresAuth: true },
|
path: '/forgot-password',
|
||||||
},
|
name: 'forgot-password',
|
||||||
{
|
component: ForgotPasswordView,
|
||||||
path: '/app/content/:id',
|
meta: { notAuthenticated: true },
|
||||||
name: 'content-item-detail',
|
},
|
||||||
component: ContentItemDetailView,
|
{
|
||||||
meta: { requiresAuth: true },
|
path: '/reset-password',
|
||||||
},
|
name: 'reset-password',
|
||||||
{
|
component: ResetPasswordView,
|
||||||
path: '/login',
|
meta: { notAuthenticated: true },
|
||||||
name: 'login',
|
props: route => ({ email: route.query.email, token: route.query.token }),
|
||||||
component: LoginView,
|
},
|
||||||
meta: { notAuthenticated: true },
|
{
|
||||||
props: route => ({ returnUrl: route.query.returnUrl || '/app/dashboard' }),
|
path: '/verify-email',
|
||||||
},
|
name: 'verify-email',
|
||||||
{
|
component: VerifyEmailView,
|
||||||
path: '/profile',
|
meta: { notAuthenticated: true },
|
||||||
redirect: { name: 'dashboard' },
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/:pathMatch(.*)*',
|
||||||
path: '/register',
|
redirect: { name: 'landing' },
|
||||||
name: 'register',
|
},
|
||||||
component: RegisterView,
|
|
||||||
meta: { requiresAuth: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/forgot-password',
|
|
||||||
name: 'forgot-password',
|
|
||||||
component: ForgotPasswordView,
|
|
||||||
meta: { notAuthenticated: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/reset-password',
|
|
||||||
name: 'reset-password',
|
|
||||||
component: ResetPasswordView,
|
|
||||||
meta: { notAuthenticated: true },
|
|
||||||
props: route => ({ email: route.query.email, token: route.query.token }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/verify-email',
|
|
||||||
name: 'verify-email',
|
|
||||||
component: VerifyEmailView,
|
|
||||||
meta: { notAuthenticated: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/:pathMatch(.*)*',
|
|
||||||
redirect: { name: 'landing' },
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Navigation guards
|
// Navigation guards
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||||
if (!authStore.isAuthenticated) {
|
if (!authStore.isAuthenticated) {
|
||||||
next({
|
next({
|
||||||
name: 'login',
|
name: 'login',
|
||||||
query: { returnUrl: to.fullPath },
|
query: { returnUrl: to.fullPath },
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
const requiredRoles = to.matched.flatMap(record => record.meta.roles ?? []);
|
|
||||||
if (requiredRoles.length > 0 && !authStore.hasAnyRole(requiredRoles)) {
|
|
||||||
next({ name: 'dashboard' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
} else if (to.matched.some(record => record.meta.notAuthenticated)) {
|
|
||||||
if (authStore.isAuthenticated) next({ name: 'dashboard' });
|
|
||||||
else next();
|
|
||||||
} else {
|
} else {
|
||||||
next();
|
const requiredRoles = to.matched.flatMap(record => record.meta.roles ?? []);
|
||||||
|
if (requiredRoles.length > 0 && !authStore.hasAnyRole(requiredRoles)) {
|
||||||
|
next({ name: 'dashboard' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
} else if (to.matched.some(record => record.meta.notAuthenticated)) {
|
||||||
|
if (authStore.isAuthenticated) next({ name: 'dashboard' });
|
||||||
|
else next();
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
Reference in New Issue
Block a user