Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ logs
*.log
Dockerfile
.dockerignore
drizzle
app/db/seed/seedTests/*.json
.cache
.idea/*
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ playground.*
coverage/*
app/config.json
drizzle
app/db/seed/seedTests/*.json
app/db/seed/seedTests/*.json
uploads/
206 changes: 206 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Database Migrations Guide

This document explains how database migrations work in Checkmate and how to add new schema changes.

## Overview

Checkmate uses [Drizzle ORM](https://orm.drizzle.team/) for database migrations. The migration system is designed to work seamlessly with both:

- **Fresh Docker deployments** (new databases)
- **Existing databases** (upgrades)

## How It Works

```
┌─────────────────────────────────────────────────────────────────┐
│ Migration Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Fresh Deployment Existing Database │
│ ───────────────── ───────────────── │
│ │ │ │
│ ▼ │ │
│ seedData.sql runs │ │
│ (creates all tables) │ │
│ │ │ │
│ ▼ ▼ │
│ migration-runner.ts ◄────────► migration-runner.ts │
│ │ │ │
│ ▼ ▼ │
│ Detects tables exist Checks __drizzle_migrations │
│ Marks as "baseline" Runs only pending migrations │
│ │ │ │
│ ▼ ▼ │
│ App Starts App Starts │
│ │
└─────────────────────────────────────────────────────────────────┘
```

## Migration Files

Migrations are stored in the `drizzle/` directory:

```
drizzle/
├── 0000_lush_lyja.sql # First migration (attachments)
├── 0001_next_migration.sql # Future migrations...
└── meta/
├── _journal.json # Migration registry
└── 0000_snapshot.json # Schema snapshots
```

## Adding New Migrations

### Step 1: Update the Schema

Edit or create schema files in `app/db/schema/`:

```typescript
// app/db/schema/newFeature.ts
import { mysqlTable, int, varchar } from 'drizzle-orm/mysql-core'

export const newTable = mysqlTable('newTable', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }).notNull(),
// ... more columns
})
```

### Step 2: Generate Migration

```bash
yarn db:generate
```

This creates a new migration file in `drizzle/` with only the incremental changes.

### Step 3: Review the Migration

Check the generated SQL file to ensure it only contains your new changes:

```bash
cat drizzle/0001_*.sql
```

### Step 4: Test Locally

```bash
# Apply migration to your local database
yarn db:migrate

# Or use the production runner
yarn db:migrate:run
```

### Step 5: Update seedData.sql

For fresh deployments, add the new table(s) to `seedData.sql`:

```sql
-- Add after existing table definitions

CREATE TABLE `newTable` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```

### Step 6: Commit and Deploy

```bash
git add drizzle/ seedData.sql app/db/schema/
git commit -m "feat: add newTable migration"
```

## Available Commands

| Command | Description |
|---------|-------------|
| `yarn db:generate` | Generate migration from schema changes |
| `yarn db:migrate` | Run migrations (Drizzle default) |
| `yarn db:migrate:run` | Run migrations (production runner with baseline support) |
| `yarn db:seed` | Seed database with test data |

## Docker Deployment

When Docker containers start, the entrypoint script automatically:

1. Waits for database connectivity
2. Runs `migration-runner.ts`
3. Starts the application

```bash
# Deploy with automatic migrations
docker-compose up --build -d

# Check migration logs
docker logs checkmate-app | grep -A 20 "Migration"
```

## Troubleshooting

### "Table already exists" Error

This happens when Drizzle tries to create tables that already exist. Solutions:

1. **For development**: Drop and recreate the database
2. **For production**: The migration-runner handles this automatically by detecting existing tables

### Migration Not Applied

Check if the migration is tracked:

```sql
SELECT * FROM __drizzle_migrations;
```

If missing, you can manually mark it:

```sql
INSERT INTO __drizzle_migrations (hash, created_at)
VALUES ('0001_migration_tag', UNIX_TIMESTAMP() * 1000);
```

### Reset Migrations (Development Only)

```bash
# Remove migration tracking
mysql -e "DROP TABLE IF EXISTS __drizzle_migrations" your_database

# Remove migration files
rm -rf drizzle/

# Regenerate from current schema
yarn db:generate
```

## Best Practices

1. **One Change Per Migration**: Keep migrations focused on a single feature
2. **Test Before Deploy**: Always test migrations on a copy of production data
3. **Backwards Compatible**: Avoid breaking changes to existing columns
4. **Document Changes**: Add comments in migration SQL files
5. **Keep seedData.sql Updated**: Always sync fresh deployment schema

## Architecture

```
┌────────────────────┐ ┌────────────────────┐
│ Schema Files │ │ Migration Files │
│ app/db/schema/* │────▶│ drizzle/*.sql │
└────────────────────┘ └────────────────────┘
│ │
│ ▼
│ ┌────────────────────┐
│ │ migration-runner │
│ │ (Docker startup) │
│ └────────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ seedData.sql │ │ MySQL Database │
│ (fresh deploys) │────▶│ __drizzle_migrations
└────────────────────┘ └────────────────────┘
```

27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This Test Case Management Tool is designed to address the challenges faced by te

- [Detailed Documentation](https://checkmate.dreamhorizon.org/)
- [Installation Guide](https://checkmate.dreamhorizon.org/docs/project/setup)
- [Cloud Deployment (AWS/GCP)](https://checkmate.dreamhorizon.org/docs/project/cloud-deployment)
- [Product Guide](https://checkmate.dreamhorizon.org/docs/guides/projects)
- [API Documentation](https://checkmate.dreamhorizon.org/docs/guides/api/)
- [Connect with us on discord](https://discord.gg/wBQXeYAKNc)
Expand Down Expand Up @@ -105,6 +106,32 @@ cd checkmate
5. App will be started on http://localhost:3000


### ☁️ Production Deployment (AWS/GCP)

Deploy Checkmate to production on AWS or GCP. See our [Cloud Deployment Guide](https://checkmate.dreamhorizon.org/docs/project/cloud-deployment) for detailed instructions.

**Quick Overview:**

| Platform | Options |
|----------|---------|
| **AWS** | EC2 + Docker, ECS (Fargate), RDS for MySQL |
| **GCP** | Compute Engine, Cloud Run, Cloud SQL |

**Key Steps:**
1. Configure production Google OAuth credentials
2. Set up managed database (RDS/Cloud SQL) or use Docker MySQL
3. Deploy with Docker Compose or container service
4. Configure SSL with Let's Encrypt or cloud provider certificates
5. Set up monitoring and backups

```bash
# Example: Deploy on any Linux server with Docker
git clone https://github.com/dream-horizon-org/checkmate.git
cd checkmate
cp .env.example .env # Configure production values
docker-compose up -d --build
```

### 📖 API Documentation

[Postman](https://documenter.getpostman.com/view/23217307/2sAYXFgwRt) collection of APIs is currently available, comprehensive documentation is in progress.
Expand Down
44 changes: 44 additions & 0 deletions app/components/Attachments/AttachmentSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Attachment Section Component
*
* A section wrapper for displaying attachments with a title and optional actions.
*/

import {cn} from '~/ui/utils'
import {MediaGallery} from './MediaGallery'
import {AttachmentSectionProps} from './types'

export function AttachmentSection({
title,
attachments,
onDelete,
canDelete = false,
children,
emptyMessage,
}: AttachmentSectionProps) {
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700 flex items-center gap-2">
{title}
{attachments.length > 0 && (
<span className="px-2 py-0.5 text-xs font-medium bg-slate-100 text-slate-600 rounded-full">
{attachments.length}
</span>
)}
</h3>
{children}
</div>

<MediaGallery
attachments={attachments}
onDelete={onDelete}
canDelete={canDelete}
emptyMessage={emptyMessage}
/>
</div>
)
}

export default AttachmentSection

61 changes: 61 additions & 0 deletions app/components/Attachments/DeleteConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Delete Confirmation Dialog
*
* Shows a confirmation dialog before deleting an attachment.
*/

import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '~/ui/alert-dialog'
import {Attachment} from './types'

interface DeleteConfirmDialogProps {
attachment: Attachment | null
isOpen: boolean
onClose: () => void
onConfirm: () => void
}

export function DeleteConfirmDialog({
attachment,
isOpen,
onClose,
onConfirm,
}: DeleteConfirmDialogProps) {
if (!attachment) return null

return (
<AlertDialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Attachment?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete{' '}
<span className="font-medium text-slate-700">
"{attachment.originalFilename}"
</span>
? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={onClose}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={onConfirm}
className="bg-red-600 hover:bg-red-700 focus:ring-red-600">
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

export default DeleteConfirmDialog

Loading