Shared DTOs, authentication, validation, and test utilities for WXYC services.
This repository includes a setup script to quickly bootstrap the entire WXYC development environment.
flowchart TB
subgraph Frontend
DJ[dj-site<br/>localhost:3000]
end
subgraph Backend-Service
API[Backend API<br/>localhost:8080]
Auth[Auth Service<br/>localhost:8082]
end
subgraph Database
PG[(PostgreSQL<br/>localhost:5432)]
end
DJ --> API
DJ --> Auth
API --> PG
Auth --> PG
# Clone this repository
git clone git@github.com:WXYC/wxyc-shared.git
cd wxyc-shared
# Run the setup script
./scripts/setup-dev-environment.shThe script will:
- Check for required dependencies (Docker, Node.js, npm, git)
- Clone Backend-Service and dj-site repositories (if not present)
- Install npm dependencies
- Start PostgreSQL database
- Start backend and auth services
- Start the frontend
- Verify all services with health checks
# Show help
./scripts/setup-dev-environment.sh --help
# Skip repository cloning (if already cloned)
./scripts/setup-dev-environment.sh --skip-clone
# Skip npm install (if dependencies are current)
./scripts/setup-dev-environment.sh --skip-deps
# Start only backend services
./scripts/setup-dev-environment.sh --backend-only
# Start only frontend (assumes backend is running)
./scripts/setup-dev-environment.sh --frontend-only| Variable | Default | Description |
|---|---|---|
WXYC_DEV_ROOT |
.. |
Directory containing/for WXYC repositories |
BACKEND_BRANCH |
main |
Backend-Service branch to checkout |
FRONTEND_BRANCH |
main |
dj-site branch to checkout |
| Service | URL | Expected Response |
|---|---|---|
| Backend | http://localhost:8080/healthcheck | 200 OK |
| Auth | http://localhost:8082/auth/ok | 200 OK |
| Frontend | http://localhost:3000 | 200 OK |
Once running, log in with any of these accounts (password: testpassword123):
| Username | Role |
|---|---|
| test_member | member |
| test_dj1 | dj |
| test_dj2 | dj |
| test_music_director | musicDirector |
| test_station_manager | stationManager |
This package serves as the single source of truth for:
- DTOs: TypeScript interfaces for all API request/response types (generated from OpenAPI)
- Auth Client: Better Auth client with role hierarchy, capabilities, and branded authorization types
- Validation: Shared validation utilities for consistent validation across services
- Test Utilities: Shared fixtures, factories, and assertions
- E2E Tests: End-to-end tests that verify full-stack integration
This package is published to GitHub Packages (not public npm).
Create or update .npmrc in your project root:
@wxyc:registry=https://npm.pkg.github.com
You need a GitHub Personal Access Token with read:packages scope.
For local development, authenticate once:
npm login --registry=https://npm.pkg.github.com --scope=@wxyc
# Username: your-github-username
# Password: your-personal-access-token
# Email: your-emailFor CI (GitHub Actions), add to your workflow:
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://npm.pkg.github.com'
scope: '@wxyc'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}npm install @wxyc/sharedimport {
FlowsheetEntryResponse,
AlbumSearchResult,
isFlowsheetSongEntry,
} from '@wxyc/shared/dtos';
// Type your API responses
const entries: FlowsheetEntryResponse[] = await fetchFlowsheet();
// Use type guards
for (const entry of entries) {
if (isFlowsheetSongEntry(entry)) {
console.log(`${entry.artist_name} - ${entry.track_title}`);
}
}import {
createTestAlbum,
createTestFlowsheetEntry,
assertValidFlowsheetEntry,
resetIdCounter,
} from '@wxyc/shared/test-utils';
describe('MyComponent', () => {
beforeEach(() => {
resetIdCounter();
});
it('should display album', () => {
const album = createTestAlbum({ album_title: 'Custom Title' });
// ...
});
});import {
isValidEmail,
validateEmail,
EMAIL_REGEX,
} from '@wxyc/shared/validation';
// Simple boolean check
if (isValidEmail(userInput)) {
// proceed
}
// Structured validation with error messages
const result = validateEmail(userInput);
if (!result.valid) {
showError(result.error); // "Email is required" or "Invalid email format"
}
// Use the regex directly if needed
const isValid = EMAIL_REGEX.test(email);import {
// Pre-configured Better Auth client
authClient,
createWXYCAuthClient,
getJWTToken,
// Role checking
Authorization,
roleToAuthorization,
checkRole,
// Capability checking
checkCapability,
hasCapability,
// Permission system
hasPermission,
canManageRoster,
} from '@wxyc/shared/auth-client';
// Check authorization level
const auth = roleToAuthorization(user.role); // "stationManager" -> Authorization.SM
if (auth >= Authorization.DJ) {
// User is at least a DJ
}
// Compile-time enforced authorization checks
const result = checkRole(user, Authorization.SM);
if (!result.authorized) {
return <AccessDenied reason={result.reason} />;
}
// result.user is now branded as RoleAuthorizedUser<Authorization.SM>
// Check capabilities (cross-cutting permissions)
const editorCheck = checkCapability(user, "editor");
if (editorCheck.authorized) {
// User can edit website content
}
// Resource-based permissions
if (hasPermission(user.role, "catalog", "write")) {
// User can modify the music catalog
}| Module | Description |
|---|---|
flowsheet.dto |
Flowsheet entries, shows, on-air status |
catalog.dto |
Albums, artists, search results |
rotation.dto |
Rotation entries and frequencies |
schedule.dto |
DJ schedule and shifts |
dj.dto |
DJ profiles, bins, playlists |
request.dto |
Song requests, device auth |
metadata.dto |
External metadata (Discogs, Spotify) |
common.dto |
Shared types (errors, pagination, genres) |
| Module | Description |
|---|---|
fixtures |
Static test data for common entities |
factories |
Factory functions with override support |
assertions |
Custom assertion helpers |
| Export | Description |
|---|---|
EMAIL_REGEX |
Regex pattern for email validation |
isValidEmail(email) |
Returns true if valid email format |
validateEmail(email) |
Returns { valid, error? } with detailed result |
ValidationResult |
TypeScript type for validation results |
| Export | Description |
|---|---|
authClient |
Pre-configured Better Auth client |
createWXYCAuthClient(baseURL) |
Factory to create auth client |
getJWTToken() |
Get JWT token for API calls |
| Authorization | |
Authorization |
Enum: NO, DJ, MD, SM, ADMIN |
roleToAuthorization(role) |
Convert role string to Authorization |
authorizationToRole(auth) |
Convert Authorization to role string |
checkRole(user, level) |
Check role, returns branded user type |
checkCapability(user, cap) |
Check capability, returns branded user type |
| Roles & Permissions | |
ROLES |
All WXYC roles ordered by privilege |
WXYCRole |
Type for role strings |
hasPermission(role, resource, action) |
Check resource permission |
canManageRoster(role) |
Check roster management access |
canAssignRoles(role) |
Check if can assign roles to others |
getAssignableRoles(role) |
Get roles this role can assign |
| Capabilities | |
CAPABILITIES |
Cross-cutting capabilities: editor, webmaster |
Capability |
Type for capability strings |
hasCapability(caps, cap) |
Check if user has capability |
canAssignCapability(user, cap) |
Check if user can assign capability |
# Install dependencies
npm install
# Build
npm run build
# Run unit tests
npm test
# Run E2E tests (requires running services)
npm run test:e2e
# Type check
npm run lintSee e2e/README.md for details on running E2E tests.
See railway/ for the Railway service topology, environment variable reference, and setup instructions for replicating the deployment environment.
- Add new DTOs in
src/dtos/ - Export them from
src/dtos/index.ts - Add corresponding test fixtures/factories
- Run
npm run lintto verify types