database-integration

SKILL.md

Database Integration

Purpose

Implement reliable and efficient database operations using the ORM, including queries, transactions, and migrations.

When to Use

  • Data model implementation
  • Query optimization
  • Transaction handling
  • Migration writing

Prerequisites

  • Database schema defined
  • ORM configured (Prisma)
  • Connection pooling set up

Process

Step 1: Define Schema

Prisma schema:
- Models
- Relations
- Indexes
- Constraints

Step 2: Generate Client

Commands:
- prisma generate
- prisma migrate dev

Step 3: Implement Repository

Repository pattern:
- CRUD operations
- Complex queries
- Transactions

Step 4: Optimize Queries

Optimization:
- Select only needed fields
- Use indexes
- Avoid N+1 queries
- Use transactions

Step 5: Handle Errors

Error handling:
- Constraint violations
- Connection errors
- Deadlocks
- Timeouts

Inputs

Input Type Required Description
database_schema SQL Yes Schema design
queries Markdown Yes Required queries

Outputs

Output Type Description
schema.prisma Prisma Schema definition
migrations/ SQL Migration files
repositories/ TypeScript Data access

StudyAbroad-Specific Considerations

  • User data privacy
  • Application audit trail
  • Soft deletes for GDPR
  • Efficient search queries

Integration Points

  • Architecture Agent: Schema design
  • Security Agent: Data protection
  • DevOps Agent: Migrations

Examples

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(uuid())
  email         String    @unique
  passwordHash  String?
  firstName     String
  lastName      String
  dateOfBirth   DateTime?
  nationality   String?
  gdprConsent   Boolean   @default(false)
  gdprConsentAt DateTime?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  deletedAt     DateTime? // Soft delete for GDPR

  applications  Application[]
  documents     Document[]
  oauthAccounts OAuthAccount[]

  @@index([email])
  @@index([deletedAt])
}

model Application {
  id          String    @id @default(uuid())
  userId      String
  programId   String
  status      String    @default("draft")
  progress    Int       @default(0)
  submittedAt DateTime?
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  user        User      @relation(fields: [userId], references: [id])
  program     Program   @relation(fields: [programId], references: [id])
  documents   Document[]
  statusHistory ApplicationStatusHistory[]

  @@unique([userId, programId])
  @@index([userId])
  @@index([status])
}

model ApplicationStatusHistory {
  id            String   @id @default(uuid())
  applicationId String
  oldStatus     String?
  newStatus     String
  changedBy     String?
  notes         String?
  createdAt     DateTime @default(now())

  application   Application @relation(fields: [applicationId], references: [id])

  @@index([applicationId])
}
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient, Prisma } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super({
      log: [
        { emit: 'event', level: 'query' },
        { emit: 'stdout', level: 'error' },
        { emit: 'stdout', level: 'warn' }
      ]
    })
  }

  async onModuleInit() {
    await this.$connect()
    
    // Soft delete middleware
    this.$use(async (params, next) => {
      if (params.model === 'User') {
        if (params.action === 'delete') {
          params.action = 'update'
          params.args['data'] = { deletedAt: new Date() }
        }
        if (params.action === 'deleteMany') {
          params.action = 'updateMany'
          if (params.args.data !== undefined) {
            params.args.data['deletedAt'] = new Date()
          } else {
            params.args['data'] = { deletedAt: new Date() }
          }
        }
        // Filter out soft-deleted by default
        if (params.action === 'findUnique' || params.action === 'findFirst') {
          params.action = 'findFirst'
          params.args.where['deletedAt'] = null
        }
        if (params.action === 'findMany') {
          if (params.args.where) {
            if (params.args.where.deletedAt === undefined) {
              params.args.where['deletedAt'] = null
            }
          } else {
            params.args['where'] = { deletedAt: null }
          }
        }
      }
      return next(params)
    })
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }

  // Transaction helper
  async executeInTransaction<T>(
    fn: (prisma: Prisma.TransactionClient) => Promise<T>
  ): Promise<T> {
    return this.$transaction(fn, {
      maxWait: 5000,
      timeout: 10000,
      isolationLevel: Prisma.TransactionIsolationLevel.Serializable
    })
  }
}

// src/application/application.repository.ts
@Injectable()
export class ApplicationRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findByUserWithPrograms(
    userId: string,
    options: { status?: string; page: number; limit: number }
  ) {
    const { status, page, limit } = options
    const where: Prisma.ApplicationWhereInput = { userId }
    
    if (status) {
      where.status = status
    }

    const [applications, total] = await Promise.all([
      this.prisma.application.findMany({
        where,
        include: {
          program: {
            include: {
              university: true
            }
          },
          documents: {
            select: { id: true, documentType: true, fileName: true }
          }
        },
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { createdAt: 'desc' }
      }),
      this.prisma.application.count({ where })
    ])

    return { applications, total }
  }

  async submitWithHistory(
    id: string,
    userId: string
  ): Promise<Application> {
    return this.prisma.executeInTransaction(async (tx) => {
      // Get current application
      const application = await tx.application.findFirst({
        where: { id, userId }
      })

      if (!application) {
        throw new NotFoundException()
      }

      // Update status
      const updated = await tx.application.update({
        where: { id },
        data: {
          status: 'submitted',
          submittedAt: new Date()
        }
      })

      // Create history record
      await tx.applicationStatusHistory.create({
        data: {
          applicationId: id,
          oldStatus: application.status,
          newStatus: 'submitted',
          changedBy: userId
        }
      })

      return updated
    })
  }
}

Validation

  • Schema matches design
  • Migrations run cleanly
  • Queries are efficient
  • Transactions used correctly
  • Errors handled properly
Weekly Installs
2
GitHub Stars
1
First Seen
1 day ago
Installed on
opencode2
gemini-cli2
antigravity2
claude-code2
github-copilot2
codex2