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:

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

Feature Operations

👥 Users Operations

Location: /feature/users/operations/

users.create.ts

export async function createUser(userData: NewUser): Promise<User>;
export const createRandomUser = async (index: number): Promise<NewUser>

users.find.ts

export async function findUserByEmail(email: string): Promise<User | null>;
export async function findUsers(): Promise<User[]>;

users.update.ts

export async function updateUser(
  id: string,
  data: Partial<User>,
): Promise<User>;

users.delete.ts

export async function deleteUser(id: string): Promise<void>;

🔐 Auth Operations

Location: /feature/auth/operations/

auth.login.ts

export async function loginUser(
  email: string,
  password: string,
): Promise<AuthUser>;

auth.register.ts

export async function registerUser(userData: NewUser): Promise<User>;

auth.logout.ts

export async function logoutUser(token: string): Promise<void>;

🎉 Occasions Operations

Location: /feature/occasions/operations/

occasions.create.ts

export async function createOccasion(data: NewOccasion): Promise<Occasion>;
export async function addRecipient(data: {
  occasionId: string;
  userId: string;
  relationshipId: string;
}): Promise<void>;
export const createRandomOccasion = async (index: number): Promise<NewOccasion>

occasions.find.ts

export async function findOccasionById(id: string): Promise<Occasion | null>;
export async function findOccasions(): Promise<Occasion[]>;

occasions.update.ts

export async function updateOccasion(
  id: string,
  data: Partial<Occasion>,
): Promise<Occasion>;

occasions.delete.ts

export async function removeOccasion(id: string): Promise<void>;

🎁 Gifts Operations

Location: /feature/gifts/operations/

gifts.create.ts

export async function createGift(data: NewGift): Promise<Gift>;

gifts.find.ts

export async function findAll(): Promise<Gift[]>;
export async function findById(id: string): Promise<Gift | null>;

🎯 Options Operations

Location: /feature/options/operations/

options.create.ts

export async function persistSuggestions(
  processId: string,
  suggestions: Gift[],
): Promise<void>;

options.find.ts

export const getProcessGiftSuggestions = async (processId: string)

🤔 Decisions Operations

Location: /feature/decisions/operations/

decisions.create.ts

export const selectGift = async (processId: string, giftId: string)

🔗 Relationships Operations

Location: /feature/relationships/operations/

relationships.add.ts

export async function addRelationship(
  data: NewRelationship,
): Promise<Relationship>;

relationships.find.ts

export async function findRelationships(): Promise<Relationship[]>;

📢 Notifications Operations

Location: /feature/notifications/operations/

notifications.create.ts

export async function createNotification(
  data: NewNotification,
): Promise<Notification>;

notifications.find.ts

export async function findNotifications(): Promise<Notification[]>;

notifications.send.ts

export async function sendNotification(
  notification: Notification,
): Promise<void>;

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

2. Error Handling

3. Database Operations

4. Business Logic

5. Testing Support

6. Performance

Future Enhancements

Planned Improvements

  1. Decisions Operations: Complete implementation of gift selection logic
  2. Batch Operations: Add bulk create/update/delete operations
  3. Caching: Implement result caching for frequently accessed data
  4. Transactions: Add transaction support for multi-operation workflows
  5. Auditing: Add operation logging and audit trails
  6. Validation: Enhanced business rule validation
  7. 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.