Skip to content
Open
Show file tree
Hide file tree
Changes from all 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