Code Patterns¶
Key patterns and conventions used across the Semaphore Chat codebase.
RBAC (Role-Based Access Control)¶
Controller Decorators¶
@RequiredActions(RbacActions.CREATE_MESSAGE)
@RbacResource({
type: RbacResourceType.CHANNEL,
idKey: 'channelId',
source: ResourceIdSource.PAYLOAD,
})
@Post()
async createMessage(@Body() dto: CreateMessageDto) { ... }
The RbacGuard resolves the resource (channel -> community), loads the user's roles, and checks for the required action.
Resource Types¶
| Type | Resolution |
|---|---|
CHANNEL | Looks up channel to find community |
COMMUNITY | Uses community ID directly |
INSTANCE | Instance-level permission check |
OpenAPI / Swagger¶
Prisma Enums in DTOs¶
The NestJS Swagger plugin can't introspect Prisma enum types. Fix with explicit @ApiProperty:
import { ApiProperty } from '@nestjs/swagger';
import { ChannelTypeValues } from '@/common/enums/swagger-enums';
export class ChannelDto {
@ApiProperty({ enum: ChannelTypeValues })
type: ChannelType;
}
For arrays: @ApiProperty({ enum: RbacActionsValues, isArray: true })
Controller Response Types¶
Add @ApiOkResponse({ type: FooDto }) (or @ApiCreatedResponse) so the OpenAPI spec knows the response shape. Without these, endpoints generate as 200: unknown.
PartialType Import¶
// CORRECT
import { PartialType } from '@nestjs/swagger';
// WRONG - loses Swagger metadata
import { PartialType } from '@nestjs/mapped-types';
Regenerating the API Client¶
When backend controllers or DTOs change:
docker compose run --rm backend pnpm run generate:openapi
docker compose run --rm frontend sh -c 'OPENAPI_SPEC_PATH=/spec/openapi.json pnpm exec openapi-ts'
Sensitive User Fields¶
Defense-in-depth to prevent user data leaks:
- Never return raw Prisma
Userobjects -- wrap innew UserEntity(user)which applies@Exclude()decorators - Use
PUBLIC_USER_SELECT(@/common/constants/user-select.constant) instead ofinclude: { user: true } - Test with
expectNoSensitiveUserFields()when creating DTOs that include user data
When adding new fields to the User model, update:
UserEntity@Exclude()decorators (if sensitive)SENSITIVE_USER_FIELDSconstant intest-utils/helpers/user-dto.helper.tsPUBLIC_USER_SELECTconstant (add if public, omit if sensitive)UserFactory.buildComplete()(add non-null values for testing)
WebSocket Events¶
See the dedicated WebSocket Patterns guide.
Quick rules:
- High-frequency + full payload -> direct cache update (
setQueryData) - Low-frequency + structural change -> cache invalidation (
invalidateQueries) - Transient + no persistence -> ephemeral state (
useServerEvent)
Platform Separation (Electron vs Web)¶
Detection¶
Guidelines¶
Platform-specific (use hooks): Screen capture, native file access, desktop notifications, auto-updates, system tray.
Platform-agnostic (no special handling): Voice/video (LiveKit), UI (Material-UI), state (TanStack Query), WebSocket, REST API.
Rules¶
- Never override browser APIs globally (breaks LiveKit)
- Never scatter
window.electronAPIchecks in components - Centralize platform logic in utility functions or hooks
- Let Electron intercept via
setDisplayMediaRequestHandlerin main process
Docker Development¶
All development uses Docker. Never run pnpm/npm commands on the host.