Skip to main content

User & Profile Type Definitions

Source: lib/types/user.ts and lib/types/profile.ts

These modules define types for admin users (authentication and management) and public user profiles (display and portfolio).

User Types (user.ts)

Type Aliases

UserStatus

type UserStatus = 'active' | 'inactive';

Interfaces

AuthUserData

Authentication-only user data stored in the users table. This is the minimal user representation used for auth flows.

interface AuthUserData {
id: string;
email: string;
username?: string;
name?: string;
title?: string;
avatar?: string;
role?: string;
roleName?: string;
status?: UserStatus;
created_by?: string;
created_at: string;
updated_at: string;
}

UserData

Full user data that includes profile information from clientProfiles. This is the complete user representation used throughout the admin dashboard.

interface UserData {
id: string;
username: string;
email: string;
name: string;
title?: string;
avatar?: string;
role: string;
roleName?: string;
status: UserStatus;
created_at: string;
updated_at: string;
created_by: string;
}

Difference from AuthUserData: In UserData, username, name, role, status, and created_by are all required (non-optional).

CreateUserRequest

Payload for creating a new admin user.

interface CreateUserRequest {
username: string;
email: string;
name: string;
title?: string;
avatar?: string;
role: string;
password: string;
}

UpdateUserRequest

Payload for updating an existing user. All fields are optional.

interface UpdateUserRequest {
username?: string;
email?: string;
name?: string;
title?: string;
avatar?: string;
role?: string;
status?: UserStatus;
}

AuthUserListResponse

Paginated response for authentication user lists.

interface AuthUserListResponse {
users: AuthUserData[];
total: number;
page: number;
limit: number;
totalPages: number;
}

UserListResponse

Paginated response for full user lists.

interface UserListResponse {
users: UserData[];
total: number;
page: number;
limit: number;
totalPages: number;
}

UserResponse

Single user response wrapper.

interface UserResponse {
user: UserData;
}

UserListOptions

Query parameters for filtering and paginating user lists.

interface UserListOptions {
includeInactive?: boolean;
sortBy?: 'name' | 'username' | 'email' | 'role' | 'created_at';
sortOrder?: 'asc' | 'desc';
page?: number;
limit?: number;
search?: string;
role?: string;
status?: UserStatus;
}

UserWithCount

Extended user data with an optional count field for statistics.

interface UserWithCount extends UserData {
count?: number;
}

Zod Validation Schemas

userValidationSchema

Full validation schema for creating a user:

const userValidationSchema = z.object({
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(30, 'Username must be less than 30 characters')
.regex(/^[a-zA-Z0-9_-]+$/,
'Username can only contain letters, numbers, hyphens, and underscores'),
email: z.string()
.email('Invalid email address')
.max(255, 'Email must be less than 255 characters'),
name: z.string()
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name must be less than 100 characters'),
title: z.string()
.max(100, 'Title must be less than 100 characters')
.optional(),
role: z.string()
.min(1, 'Role is required'),
status: z.enum(['active', 'inactive']).optional(),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
'Password must contain at least one lowercase letter, '
+ 'one uppercase letter, and one number'),
});

updateUserValidationSchema

Partial schema for updates (excludes password):

const updateUserValidationSchema = userValidationSchema
.partial()
.omit({ password: true });

Validation Constants

const USER_VALIDATION = {
USERNAME_MIN_LENGTH: 3,
USERNAME_MAX_LENGTH: 30,
NAME_MIN_LENGTH: 2,
NAME_MAX_LENGTH: 100,
TITLE_MAX_LENGTH: 100,
EMAIL_MAX_LENGTH: 255,
PASSWORD_MIN_LENGTH: 8,
AVATAR_MAX_SIZE: 2 * 1024 * 1024, // 2MB
AVATAR_ALLOWED_TYPES: ['image/jpeg', 'image/png', 'image/webp'],
} as const;

Helper Functions

isValidUserStatus

Type guard that narrows a string to UserStatus:

function isValidUserStatus(status: string): status is UserStatus {
return status === 'active' || status === 'inactive';
}

generateUserId

Generates a UUID for a new user:

function generateUserId(): string {
return crypto.randomUUID();
}

formatDateForYaml

Formats a Date as a YAML-compatible string (YYYY-MM-DD HH:mm):

function formatDateForYaml(date: Date = new Date()): string;

// Example: formatDateForYaml(new Date('2025-03-15T10:30:00'))
// => "2025-03-15 10:30"

Profile Types (profile.ts)

Profile

Public-facing user profile structure used for the profile display page.

interface Profile {
username: string;
displayName: string;
bio: string;
avatar: string;
location: string;
company: string;
jobTitle: string;
website: string;
socialLinks: Array<{
platform: string;
url: string;
displayName: string;
}>;
skills: Array<{
name: string;
level: number;
}>;
interests: string[];
portfolio: Array<{
id: string;
title: string;
description: string;
imageUrl: string;
externalUrl: string;
tags: string[];
isFeatured: boolean;
}>;
themeColor: string;
isPublic: boolean;
memberSince: string;
submissions: Array<{
id: string;
title: string;
description: string;
category: string;
status: 'approved' | 'pending' | 'rejected';
submittedAt: string;
updatedAt: string;
url: string;
imageUrl?: string;
}>;
}

Nested types explained:

  • socialLinks - Links to external social platforms (GitHub, LinkedIn, Twitter, etc.)
  • skills - Skill entries with a proficiency level (e.g., 1-5 or 1-100)
  • portfolio - Showcase items with optional featured flag for prominent display
  • submissions - Items submitted by this user, with their current review status

Usage Examples

Creating a user with validation

import { userValidationSchema } from '@/lib/types/user';
import type { CreateUserRequest } from '@/lib/types/user';

const input: CreateUserRequest = {
username: 'johndoe',
email: 'john@example.com',
name: 'John Doe',
role: 'editor',
password: 'SecurePass1',
};

const result = userValidationSchema.safeParse(input);
if (!result.success) {
console.error(result.error.flatten());
}

Filtering users

import type { UserListOptions } from '@/lib/types/user';

const options: UserListOptions = {
role: 'editor',
status: 'active',
sortBy: 'created_at',
sortOrder: 'desc',
page: 1,
limit: 25,
search: 'john',
};

Using the profile type

import type { Profile } from '@/lib/types/profile';

function renderProfile(profile: Profile) {
const visibleSkills = profile.skills
.filter(s => s.level >= 3)
.sort((a, b) => b.level - a.level);

const featuredPortfolio = profile.portfolio
.filter(p => p.isFeatured);

return { visibleSkills, featuredPortfolio };
}
  • RoleData from role.ts defines the roles referenced in UserData.role
  • ClientProfileWithAuth from lib/db/queries is the database-level user profile representation
  • Profile is the public-facing profile, while UserData is the admin-facing representation