Operations
This document outlines all business logic operations across the API features. Operations represent the core business logic layer that sits between controllers and the database, handling data manipulation, validation, and business rules.
Overview
The API follows a clean architecture with 19 operations organized by feature domains:
- Auth (3 operations): login, logout, register
- Decisions (1 operation): create
- Gifts (2 operations): create, find
- Notifications (3 operations): create, find, send
- Occasions (4 operations): create, delete, find, update
- Options (2 operations): create, find
- Relationships (2 operations): add, find
- Users (3 operations): create, delete, find, update
Operation Architecture
Core Pattern
All operations follow this consistent structure:
import { db } from "@/lib/drizzle";
import { entitySchema } from "../feature.schema";
import { NewEntity, Entity } from "../feature.types";
export async function createEntity(data: NewEntity): Promise<Entity> {
const [newEntity] = await db
.insert(entitySchema)
.values({
// Field mapping and validation
field: data.field,
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();
return newEntity;
}
export async function findEntity(id: string): Promise<Entity | null> {
const entity = await db.query.entitySchema.findFirst({
where: eq(entitySchema.id, id),
});
return entity || null;
}
Key Characteristics
- Database Operations: Direct database interactions using Drizzle ORM
- Business Logic: Core business rules and validation
- Type Safety: Full TypeScript support with generated types
- Error Handling: Proper error throwing and validation
- Data Transformation: Data mapping and formatting
- Utility Functions: Helper functions for testing and seeding
Feature Operations
👥 Users Operations
Location: /feature/users/operations/
users.create.ts
export async function createUser(userData: NewUser): Promise<User>;
- Purpose: Creates a new user in the system
- Input:
NewUser- User creation data - Output:
User- Created user with generated ID - Features: UUID generation, field validation, timestamp handling
export const createRandomUser = async (index: number): Promise<NewUser>
- Purpose: Generates random user data for testing/seeding
- Input:
index- Sequential index for variation - Output:
NewUser- Randomly generated user data - Features: Faker.js integration, realistic data generation
users.find.ts
export async function findUserByEmail(email: string): Promise<User | null>;
- Purpose: Finds user by email address
- Input:
email- User email to search - Output:
User | null- User data or null if not found - Features: Email lookup, null handling
export async function findUsers(): Promise<User[]>;
- Purpose: Retrieves all users from the system
- Output:
User[]- Array of all users - Features: Bulk data retrieval
users.update.ts
export async function updateUser(
id: string,
data: Partial<User>,
): Promise<User>;
- Purpose: Updates existing user information
- Input:
id- User ID,data- Partial update data - Output:
User- Updated user data - Features: Partial updates, timestamp management
users.delete.ts
export async function deleteUser(id: string): Promise<void>;
- Purpose: Removes user from the system
- Input:
id- User ID to delete - Output:
void- No return value - Features: Soft delete consideration, cleanup
🔐 Auth Operations
Location: /feature/auth/operations/
auth.login.ts
export async function loginUser(
email: string,
password: string,
): Promise<AuthUser>;
- Purpose: Authenticates user credentials
- Input:
email,password- User credentials - Output:
AuthUser- Authenticated user data - Features: Password verification, error handling, security checks
auth.register.ts
export async function registerUser(userData: NewUser): Promise<User>;
- Purpose: Registers new user account
- Input:
NewUser- Registration data - Output:
User- Created user account - Features: Account creation, validation, password hashing
auth.logout.ts
export async function logoutUser(token: string): Promise<void>;
- Purpose: Logs out user and invalidates session
- Input:
token- JWT token to invalidate - Output:
void- No return value - Features: Token invalidation, session cleanup
🎉 Occasions Operations
Location: /feature/occasions/operations/
occasions.create.ts
export async function createOccasion(data: NewOccasion): Promise<Occasion>;
- Purpose: Creates a new occasion/event
- Input:
NewOccasion- Occasion creation data - Output:
Occasion- Created occasion with metadata - Features: Date handling, status management, organizer assignment
export async function addRecipient(data: {
occasionId: string;
userId: string;
relationshipId: string;
}): Promise<void>;
- Purpose: Adds participant to an occasion
- Input: Participant relationship data
- Output:
void- No return value - Features: Conflict handling, participant tracking
export const createRandomOccasion = async (index: number): Promise<NewOccasion>
- Purpose: Generates random occasion data for testing
- Input:
index- Sequential index for variation - Output:
NewOccasion- Randomly generated occasion - Features: Realistic occasion names, dates, descriptions
occasions.find.ts
export async function findOccasionById(id: string): Promise<Occasion | null>;
- Purpose: Finds occasion by ID
- Input:
id- Occasion ID - Output:
Occasion | null- Occasion data or null - Features: ID lookup, null handling
export async function findOccasions(): Promise<Occasion[]>;
- Purpose: Retrieves all occasions
- Output:
Occasion[]- Array of all occasions - Features: Bulk retrieval, filtering capabilities
occasions.update.ts
export async function updateOccasion(
id: string,
data: Partial<Occasion>,
): Promise<Occasion>;
- Purpose: Updates existing occasion
- Input:
id- Occasion ID,data- Update data - Output:
Occasion- Updated occasion - Features: Partial updates, timestamp management
occasions.delete.ts
export async function removeOccasion(id: string): Promise<void>;
- Purpose: Removes occasion from system
- Input:
id- Occasion ID to delete - Output:
void- No return value - Features: Cascade deletion considerations
🎁 Gifts Operations
Location: /feature/gifts/operations/
gifts.create.ts
export async function createGift(data: NewGift): Promise<Gift>;
- Purpose: Creates a new gift entry
- Input:
NewGift- Gift creation data - Output:
Gift- Created gift with metadata - Features: Price validation, category assignment
gifts.find.ts
export async function findAll(): Promise<Gift[]>;
- Purpose: Retrieves all gifts from catalog
- Output:
Gift[]- Array of all gifts - Features: Catalog retrieval, sorting options
export async function findById(id: string): Promise<Gift | null>;
- Purpose: Finds specific gift by ID
- Input:
id- Gift ID - Output:
Gift | null- Gift data or null - Features: Individual gift lookup
🎯 Options Operations
Location: /feature/options/operations/
options.create.ts
export async function persistSuggestions(
processId: string,
suggestions: Gift[],
): Promise<void>;
- Purpose: Persists gift suggestions for a process
- Input:
processId,suggestions- Process and gift suggestions - Output:
void- No return value - Features: Bulk insertion, process tracking
options.find.ts
export const getProcessGiftSuggestions = async (processId: string)
- Purpose: Gets random gift suggestions for a process
- Input:
processId- Process identifier - Output:
Gift[]- Array of suggested gifts - Features: Random selection, configurable limits, logging
🤔 Decisions Operations
Location: /feature/decisions/operations/
decisions.create.ts
export const selectGift = async (processId: string, giftId: string)
- Purpose: Selects a gift for a decision process
- Input:
processId,giftId- Process and gift identifiers - Output:
void- No return value - Status: TODO - Placeholder implementation
- Planned Features: Gift validation, process status updates, workflow triggers
🔗 Relationships Operations
Location: /feature/relationships/operations/
relationships.add.ts
export async function addRelationship(
data: NewRelationship,
): Promise<Relationship>;
- Purpose: Creates relationship between users
- Input:
NewRelationship- Relationship data - Output:
Relationship- Created relationship - Features: User linking, relationship type validation
relationships.find.ts
export async function findRelationships(): Promise<Relationship[]>;
- Purpose: Retrieves all relationships
- Output:
Relationship[]- Array of relationships - Features: Network graph data, filtering options
📢 Notifications Operations
Location: /feature/notifications/operations/
notifications.create.ts
export async function createNotification(
data: NewNotification,
): Promise<Notification>;
- Purpose: Creates new notification
- Input:
NewNotification- Notification data - Output:
Notification- Created notification - Features: User targeting, message formatting
notifications.find.ts
export async function findNotifications(): Promise<Notification[]>;
- Purpose: Retrieves notifications
- Output:
Notification[]- Array of notifications - Features: User filtering, read status tracking
notifications.send.ts
export async function sendNotification(
notification: Notification,
): Promise<void>;
- Purpose: Sends notification to user
- Input:
Notification- Notification to send - Output:
void- No return value - Features: Delivery tracking, status updates
Operation Patterns
CRUD Operations
Most entities follow standard CRUD patterns:
// Create
export async function createEntity(data: NewEntity): Promise<Entity>;
// Read
export async function findEntity(id: string): Promise<Entity | null>;
export async function findEntities(): Promise<Entity[]>;
// Update
export async function updateEntity(
id: string,
data: Partial<Entity>,
): Promise<Entity>;
// Delete
export async function deleteEntity(id: string): Promise<void>;
Business Logic Operations
Some operations implement specific business logic:
// Business-specific operations
export async function loginUser(
email: string,
password: string,
): Promise<AuthUser>;
export async function selectGift(
processId: string,
giftId: string,
): Promise<void>;
export async function getProcessGiftSuggestions(
processId: string,
): Promise<Gift[]>;
Utility Operations
Testing and seeding utilities:
// Random data generation
export const createRandomUser = async (index: number): Promise<NewUser>
export const createRandomOccasion = async (index: number): Promise<NewOccasion>
Database Integration
Drizzle ORM Usage
All operations use Drizzle ORM for database interactions:
import { db } from "@/lib/drizzle";
import { entitySchema } from "../feature.schema";
import { eq } from "drizzle-orm";
// Insert
const [newEntity] = await db.insert(entitySchema).values(data).returning();
// Query
const entity = await db.query.entitySchema.findFirst({
where: eq(entitySchema.id, id),
});
// Update
const [updatedEntity] = await db
.update(entitySchema)
.set(data)
.where(eq(entitySchema.id, id))
.returning();
// Delete
await db.delete(entitySchema).where(eq(entitySchema.id, id));
Type Safety
Operations leverage TypeScript types generated from database schemas:
// Generated types from Drizzle
export type NewEntity = typeof entitySchema.$inferInsert;
export type Entity = typeof entitySchema.$inferSelect;
// Used in operations for type safety
export async function createEntity(data: NewEntity): Promise<Entity>;
Error Handling
Consistent Error Patterns
export async function loginUser(
email: string,
password: string,
): Promise<AuthUser> {
const user = await findUserByEmail(email);
if (!user) {
throw new Error("Invalid email or password"); // Consistent error messages
}
if (!user.password) {
throw new Error("Account not properly set up. Please reset your password.");
}
const isPasswordValid = await comparePasswords(password, user.password);
if (!isPasswordValid) {
throw new Error("Invalid email or password");
}
return user;
}
Validation Patterns
export async function createOccasion(data: NewOccasion): Promise<Occasion> {
const [newOccasion] = await db
.insert(occasionsSchema)
.values({
name: data.name,
description: data.description || null, // Null handling
date: typeof data.date === "string" ? new Date(data.date) : data.date, // Type conversion
organizerId: data.organizerId,
status: data.status || "created", // Default values
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();
return newOccasion;
}
Testing Support
Random Data Generation
Operations include utility functions for testing:
export const createRandomUser = async (index: number): Promise<NewUser> => {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const email = faker.internet.email({
firstName,
lastName,
provider: "example.com",
});
return {
id: faker.string.uuid(),
name: `${firstName} ${lastName}`,
email: email.toLowerCase(),
age: faker.number.int({ min: 18, max: 80 }),
createdAt: faker.date.past({ years: 1 }),
updatedAt: faker.date.recent({ days: 30 }),
};
};
Seeding Support
Operations support database seeding with realistic data:
export const createRandomOccasion = async (
index: number,
): Promise<NewOccasion> => {
const occasionNames = [
"Birthday Party",
"Wedding Reception",
"Corporate Meeting",
// ... realistic names
];
return {
name: faker.helpers.arrayElement(occasionNames),
description: faker.helpers.arrayElement(occasionDescriptions),
date: faker.date.future({ years: 2, refDate: new Date() }),
organizerId: faker.string.uuid(),
status: "created" as const,
};
};
Best Practices
1. Type Safety
- Use generated types from database schemas
- Validate input types at operation boundaries
- Leverage TypeScript for compile-time error prevention
2. Error Handling
- Throw descriptive errors with clear messages
- Handle null cases appropriately
- Validate business rules before database operations
3. Database Operations
- Use Drizzle ORM query builder for type safety
- Return complete entities with all required fields
- Handle database errors gracefully
4. Business Logic
- Implement business rules in operations layer
- Keep controllers thin and focused on HTTP concerns
- Maintain separation of concerns
5. Testing Support
- Provide utility functions for test data generation
- Include realistic data patterns
- Support both unit and integration testing
6. Performance
- Use efficient database queries
- Implement proper indexing considerations
- Handle bulk operations appropriately
Future Enhancements
Planned Improvements
- Decisions Operations: Complete implementation of gift selection logic
- Batch Operations: Add bulk create/update/delete operations
- Caching: Implement result caching for frequently accessed data
- Transactions: Add transaction support for multi-operation workflows
- Auditing: Add operation logging and audit trails
- Validation: Enhanced business rule validation
- Performance: Query optimization and database indexing
Extensibility
The operations layer is designed for easy extension:
// Adding new operations
export async function advancedFeature(
data: AdvancedData,
): Promise<AdvancedResult> {
// Implementation following established patterns
}
// Extending existing operations
export async function createOccasionWithParticipants(
occasionData: NewOccasion,
participants: ParticipantData[],
): Promise<Occasion> {
// Transaction-based multi-entity creation
}
This operations layer provides a solid foundation for business logic while maintaining clean separation of concerns and type safety throughout the application.