User Repository
The UserRepository class provides the data-access layer for authentication-level user records. It wraps UserDbService with validation (via Zod schemas), uniqueness checks, and consistent error handling.
Source file: template/lib/repositories/user.repository.ts
Architecture Overview
API Route / Server Action
|
UserRepository <-- validation, uniqueness checks, error wrapping
|
UserDbService <-- database CRUD via Drizzle ORM
|
PostgreSQL / SQLite <-- users table
Unlike the Git-backed repositories (items, tags, categories), the User Repository operates directly against the relational database through UserDbService.
Class Definition
export class UserRepository {
private userDbService: UserDbService;
constructor() {
this.userDbService = new UserDbService();
}
}
Dependencies
| Import | Purpose |
|---|---|
UserDbService | Database service for user CRUD operations |
AuthUserData | Type representing an authenticated user record |
CreateUserRequest / UpdateUserRequest | Request DTOs for create and update |
UserListOptions | Filtering and pagination options |
AuthUserListResponse | Paginated response type |
userValidationSchema / updateUserValidationSchema | Zod validation schemas |
Query Methods
findAll(options?): Promise<AuthUserListResponse>
Returns a paginated list of users with optional filtering.
async findAll(options: UserListOptions = {}): Promise<AuthUserListResponse>
Return type:
interface AuthUserListResponse {
users: AuthUserData[];
total: number;
page: number;
limit: number;
totalPages: number;
}
Delegates to userDbService.findUsers(options) and maps the result into the standard paginated response shape. Wraps all database errors with a generic "Failed to retrieve users" message.
findById(id): Promise<AuthUserData | null>
Retrieves a single user by their unique identifier.
async findById(id: string): Promise<AuthUserData | null>
Returns null when no user matches.
getAllUsers(): Promise<AuthUserData[]>
Returns every user record without pagination. Intended for use in admin dropdowns, assignment lists, and similar UI elements where the full user set is needed.
async getAllUsers(): Promise<AuthUserData[]>
getStats(): Promise<UserStats>
Returns aggregate user statistics.
async getStats(): Promise<{
total: number;
active: number;
inactive: number;
}>
Delegates to userDbService.getUserStats().
usernameExists(username, excludeId?): Promise<boolean>
Checks whether a given username is already taken. Optionally excludes a specific user ID (useful during updates).
async usernameExists(username: string, excludeId?: string): Promise<boolean>
Delegates to userDbService.clientProfileUsernameExists.
emailExists(email, excludeId?): Promise<boolean>
Checks whether an email address is already in use. Performs a case-insensitive comparison by loading all users and filtering in memory.
async emailExists(email: string, excludeId?: string): Promise<boolean>
Mutation Methods
create(data): Promise<AuthUserData>
Creates a new user after validation and uniqueness enforcement.
async create(data: CreateUserRequest): Promise<AuthUserData>
Processing steps:
- Validates
emailandpasswordfields usinguserValidationSchema.pick(...)(Zod) - Checks email uniqueness via
userDbService.emailExists - Creates the user record through
userDbService.createUser
Throws an Error("Email already in use") if the email is taken.
update(id, data): Promise<AuthUserData>
Updates an existing user after validation and existence check.
async update(id: string, data: UpdateUserRequest): Promise<AuthUserData>
Processing steps:
- Validates input with
updateUserValidationSchema.parse(data)(Zod) - Verifies the user exists via
findById - Applies the update through
userDbService.updateUser
Throws an Error("User not found") if the target user does not exist.
delete(id): Promise<void>
Permanently deletes a user record.
async delete(id: string): Promise<void>
Processing steps:
- Verifies the user exists via
findById - Deletes through
userDbService.deleteUser
Throws an Error("User not found") if the target does not exist.
Note: Role-based deletion checks are handled at the profile level since
AuthUserDatacontains only authentication information.
Error Handling Pattern
All public methods follow a consistent error-handling strategy:
try {
// ... operation ...
} catch (error) {
if (error instanceof Error) {
throw error; // Re-throw domain errors (validation, not-found)
}
console.error('Error [operation]:', error);
throw new Error('Failed to [operation]'); // Generic fallback
}
This ensures domain-specific errors (email taken, user not found) propagate cleanly to API routes while unexpected errors are logged and replaced with safe messages.
Validation Schemas
The repository uses two Zod schemas from @/lib/types/user:
userValidationSchema-- full user creation schema; the repository picks onlyemailandpasswordupdateUserValidationSchema-- partial update schema that validates whichever fields are provided
Usage Example
import { UserRepository } from '@/lib/repositories/user.repository';
const userRepo = new UserRepository();
// List users with pagination
const result = await userRepo.findAll({ page: 1, limit: 25 });
// Create a new user
const newUser = await userRepo.create({
email: 'new@example.com',
password: 'securePassword123',
});
// Check availability
const taken = await userRepo.emailExists('test@example.com');
// Get statistics
const stats = await userRepo.getStats();
// => { total: 150, active: 142, inactive: 8 }
Related Files
| File | Relationship |
|---|---|
lib/services/user-db.service.ts | Underlying database service |
lib/types/user.ts | Type definitions and Zod schemas |
lib/db/drizzle.ts | Database connection and Drizzle instance |
lib/repositories/role.repository.ts | Role management (related to user permissions) |