fix(workspace-invite): inconsistence in roles names

This commit is contained in:
2026-04-30 15:45:32 -04:00
parent 07458c1541
commit ace0279bd0
7 changed files with 216 additions and 210 deletions

View File

@@ -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";
} }

View File

@@ -0,0 +1,6 @@
namespace Socialize.Api.Modules.Workspaces.Data;
public static class WorkspaceInviteStatuses
{
public const string Pending = "Pending";
}

View File

@@ -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,
}; };

View File

@@ -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>

View File

@@ -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",

View File

@@ -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",

View File

@@ -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;