Skip to content

Commit

Permalink
Use role groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ba1uev committed Feb 8, 2024
1 parent 49b72d9 commit 562ad87
Show file tree
Hide file tree
Showing 20 changed files with 338 additions and 186 deletions.
11 changes: 7 additions & 4 deletions scripts/client.build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ function getBuildConfig(): BuildOptions {
'process.env.AUTH_MESSAGE_TO_SIGN': JSON.stringify(
config.authMessageToSign
),
'process.env.ROLES': JSON.stringify(
appConfig.config.permissions.roles.map((x) => ({
...fp.pick(['id', 'name', 'accessByDefault'])(x),
lowPriority: x.id === appConfig.lowPriorityRole,
'process.env.ROLE_GROUPS': JSON.stringify(
appConfig.config.permissions.roleGroups.map((x) => ({
...x,
roles: x.roles.map((r) => ({
...fp.pick(['id', 'name', 'accessByDefault'])(r),
lowPriority: r.id === appConfig.lowPriorityRole,
})),
}))
),
},
Expand Down
24 changes: 15 additions & 9 deletions src/client/components/EntityAccessSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import config from '#client/config'
import { EntityVisibility } from '#shared/types'
import { cn } from '#client/utils'
import { USER_ROLES } from '#client/constants'
import { prop } from '#shared/utils/fp'
import React from 'react'

Expand Down Expand Up @@ -51,10 +52,10 @@ export const EntityAccessSelector: React.FC<Props> = ({
...props
}) => {
const [showAllRoles, setShowAllRoles] = React.useState(
config.roles.length <= 10
USER_ROLES.length <= 10
)
const roleIds = React.useMemo(
() => config.roles.filter(prop('accessByDefault')).map(prop('id')),
() => USER_ROLES.filter(prop('accessByDefault')).map(prop('id')),
[]
)

Expand Down Expand Up @@ -134,13 +135,18 @@ export const EntityAccessSelector: React.FC<Props> = ({
const filteredRoles = React.useMemo<
Array<{ value: string; label: string }>
>(() => {
return config.roles
.filter((x) => showAllRoles || x.accessByDefault)
.map((x) => ({
value: x.id,
label: x.name,
}))
}, [showAllRoles])
const availableRoles = USER_ROLES.map(prop('id'))
const unsupportedRoles = (props.value.allowedRoles || [])
.filter((x) => !availableRoles.includes(x))
.map((x) => ({ value: x, label: `${x} (UNSUPPORTED)` }))
const filteredRoles = USER_ROLES.filter(
(x) => showAllRoles || x.accessByDefault
).map((x) => ({
value: x.id,
label: x.name,
}))
return unsupportedRoles.concat(filteredRoles)
}, [showAllRoles, props.value.allowedRoles])

return (
<div
Expand Down
1 change: 1 addition & 0 deletions src/client/components/ui/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const Filters = <T,>(props: React.PropsWithChildren<Props<T>>) => {
}
return (
<Tag
size="small"
key={String(x.id || '~none~')}
className="inline-block cursor-pointer hover:opacity-70 mr-1 mb-1"
color={isSelected ? 'blue' : 'gray'}
Expand Down
28 changes: 17 additions & 11 deletions src/client/components/ui/UserLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,36 @@ export const UserLabel: React.FC<Props> = ({ user, hideRole = false }) => {
href={`/profile/${user.id}`}
target="_blank"
className={cn(!user.isInitialised && 'opacity-50')}
title={!user.isInitialised ? 'The user has not been onboarded yet' : undefined}
title={
!user.isInitialised
? 'The user has not been onboarded yet'
: undefined
}
kind="secondary"
>
{user.fullName}
</Link>
{!hideRole && (
<UserRoleLabel
role={user.role}
className="ml-2"
/>
)}
{!hideRole && <UserRoleLabel role={user.role} className="ml-2" />}
</span>
) : null
}

type UserRoleLabelType = {
role: User['role']
role: string
className?: string
}
export const UserRoleLabel: React.FC<UserRoleLabelType> = ({ role, ...props }) => {
export const UserRoleLabel: React.FC<UserRoleLabelType> = ({
role,
...props
}) => {
const roleRecord = USER_ROLE_BY_ID[role]
// TODO: use roleRecord.color ?
return (
<Tag className={cn(props.className)} color="gray" size="small">
<Tag
className={cn(props.className)}
size="small"
color={roleRecord ? 'gray' : 'red'}
title={roleRecord ? '' : 'Unsupported role'}
>
{roleRecord?.name || role}
</Tag>
)
Expand Down
13 changes: 9 additions & 4 deletions src/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
Layout,
ModuleClientRouter,
Office,
AppRole,
UserRole,
UserRoleGroup,
AppModule,
} from '#shared/types'

Expand All @@ -27,12 +28,16 @@ export type ClientOfficeConfig = Pick<
>

export type ClientUserRole = Pick<
AppRole,
UserRole,
'id' | 'name' | 'accessByDefault'
> & {
lowPriority: boolean
}

export type ClientUserRoleGroup = UserRoleGroup & {
roles: ClientUserRole[]
}

type ClientAppConfig = {
modules: ClientModuleConfig[]
offices: ClientOfficeConfig[]
Expand All @@ -42,7 +47,7 @@ type ClientAppConfig = {
appHost: string
mapBoxApiKey: string
layout: Layout
roles: ClientUserRole[]
roleGroups: ClientUserRoleGroup[]
auth: ClientAuthConfig
authMessageToSign: string
}
Expand All @@ -58,7 +63,7 @@ const config: ClientAppConfig = {
appHost: process.env.APP_HOST as unknown as string,
mapBoxApiKey: process.env.MAPBOX_API_KEY as unknown as string,
layout: process.env.LAYOUT as unknown as Layout,
roles: process.env.ROLES as unknown as ClientUserRole[],
roleGroups: process.env.ROLE_GROUPS as unknown as ClientUserRoleGroup[],
auth: process.env.AUTH as unknown as ClientAuthConfig,
authMessageToSign: process.env.AUTH_MESSAGE_TO_SIGN as unknown as string,
}
Expand Down
6 changes: 4 additions & 2 deletions src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export const DATE_FORMAT_DAY_NAME_FULL = 'dddd, MMMM D'

export const FRIENDLY_DATE_FORMAT = 'MMMM D YYYY'

export const USER_ROLE_BY_ID = config.roles.reduce(
export const USER_ROLES = config.roleGroups.map((x) => x.roles).flat()

export const USER_ROLE_BY_ID = USER_ROLES.reduce(
(acc, x) => ({ ...acc, [x.id]: x }),
{} as Record<string, (typeof config.roles)[0]>
{} as Record<string, (typeof USER_ROLES)[0]>
)

export const OFFICE_BY_ID = config.offices.reduce(
Expand Down
19 changes: 12 additions & 7 deletions src/client/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ import dayjs, { Dayjs } from 'dayjs'
export const cn = (
...chunks: Array<string | boolean | null | undefined>
): string => {
// return chunks.map((x) => (typeof x === 'string' ? x : '')).join(' ')
return twMerge(...chunks.map((x) => (typeof x === 'string' ? x : '')))
}

export const toggleInArray = <T>(
arr: T[],
item: T,
keepOne: boolean = false,
maxNumber?: number
maxNumber?: number,
autoDeselect?: boolean
): T[] => {
if (arr.indexOf(item) > -1) {
if (arr.includes(item)) {
if (keepOne && arr.length === 1) {
return arr
}
return arr.filter((x) => x !== item)
} else {
if (maxNumber) {
if (arr.length >= maxNumber) {
if (autoDeselect) {
return arr.slice(1).concat(item)
}
return arr
}
}
Expand Down Expand Up @@ -56,11 +59,13 @@ export const generateId = (length: number = 16, prefix?: string): string => {
// window.location.href = url.toString()
// }

export const trimString = (str: string, length: number = 32, postfix: string = '...'): string => {
export const trimString = (
str: string,
length: number = 32,
postfix: string = '...'
): string => {
if (!str) return ''
return str.length < length
? str
: str.slice(0, length) + postfix
return str.length < length ? str : str.slice(0, length) + postfix
}

export const formatDateRange = (
Expand Down
5 changes: 2 additions & 3 deletions src/modules/events/client/components/AdminEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Tag,
UserLabel,
Filters,
UserRoleLabel,
} from '#client/components/ui'
import config from '#client/config'
import { OFFICE_BY_ID, USER_ROLE_BY_ID } from '#client/constants'
Expand Down Expand Up @@ -167,9 +168,7 @@ export const AdminEvents = () => {
accessor: (event: EventAdminResponse) => (
<span className="inline-block -mr-1">
{event.allowedRoles.map((x) => (
<Tag key={x} color="gray" size="small" className="mr-1">
{USER_ROLE_BY_ID[x]?.name || x}
</Tag>
<UserRoleLabel role={x} />
))}
</span>
),
Expand Down
7 changes: 5 additions & 2 deletions src/modules/events/server/jobs/pull-global-events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UniqueConstraintError } from 'sequelize'
import { CronJob, CronJobContext } from '#server/types'
import { EntityVisibility } from '#shared/types'
import * as fp from '#shared/utils/fp'
import {
getGlobalEventDefaultChecklist,
getGlobalEventDefaultContent,
Expand Down Expand Up @@ -96,9 +97,11 @@ export const cronJob: CronJob = {
? EntityVisibility.None
: EntityVisibility.Visible

const allowedRoles = ctx.appConfig.config.permissions.roles
const allowedRoles = ctx.appConfig.config.permissions.roleGroups
.map(fp.prop('roles'))
.flat()
.filter((x) => (isInternal ? x.accessByDefault : true))
.map((x) => x.id)
.map(fp.prop('id'))

try {
await ctx.models.Event.create({
Expand Down
5 changes: 2 additions & 3 deletions src/modules/forms/client/components/AdminForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
UserLabel,
Filters,
LabelWrapper,
UserRoleLabel,
} from '#client/components/ui'
import { showNotification } from '#client/components/ui/Notifications'
import { USER_ROLE_BY_ID } from '#client/constants'
Expand Down Expand Up @@ -152,9 +153,7 @@ export const AdminForms = () => {
accessor: (form: FormAdminResponse) => (
<span className="inline-block -mr-1">
{form.allowedRoles.map((x) => (
<Tag key={x} color="gray" size="small" className="mr-1">
{USER_ROLE_BY_ID[x]?.name || x}
</Tag>
<UserRoleLabel role={x} />
))}
</span>
),
Expand Down
Loading

0 comments on commit 562ad87

Please sign in to comment.