Skip to content
Merged
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
4 changes: 3 additions & 1 deletion frontend/build-cf-pages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
if [ "$CF_PAGES_BRANCH" = "main" ]; then
export VITE_API_URL="https://tabletennis.chamika.workers.dev/api"
else
export VITE_API_URL="https://${CF_PAGES_BRANCH//\//-}-tabletennis.chamika.workers.dev/api"
BRANCH_NAME="${CF_PAGES_BRANCH//\//-}"
BRANCH_NAME="${BRANCH_NAME//_/-}"
export VITE_API_URL="https://${BRANCH_NAME}-tabletennis.chamika.workers.dev/api"
fi
echo "VITE_API_URL=$VITE_API_URL"

Expand Down
6 changes: 6 additions & 0 deletions worker/migrations/0001_remove_is_past.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Migration: Remove is_past column from fixtures table
-- The is_past field will be computed dynamically in the API layer
-- based on comparing match_date with the current date

-- Drop the is_past column from fixtures table
ALTER TABLE fixtures DROP COLUMN is_past;
72 changes: 72 additions & 0 deletions worker/migrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Database Migration: Remove is_past Column

This migration removes the `is_past` column from the fixtures table. The field will be computed dynamically in the API layer based on comparing `match_date` with the current date.

## Migration File

Location: `worker/migrations/0001_remove_is_past.sql`

## Applying the Migration

### Development Environment

```bash
cd worker
wrangler d1 migrations apply tabletennis-availability --local
```

### Staging Environment

```bash
cd worker
wrangler d1 migrations apply tabletennis-availability-staging --env staging
```

### Production Environment

```bash
cd worker
wrangler d1 migrations apply tabletennis-availability-prod --env production
```

## Verification

After applying the migration, verify the schema:

### Development
```bash
wrangler d1 execute tabletennis-availability --local --command="PRAGMA table_info(fixtures);"
```

### Staging
```bash
wrangler d1 execute tabletennis-availability-staging --env staging --command="PRAGMA table_info(fixtures);"
```

### Production
```bash
wrangler d1 execute tabletennis-availability-prod --env production --command="PRAGMA table_info(fixtures);"
```

The `is_past` column should no longer appear in the output.

## Rollback

If you need to rollback this migration, you can recreate the column with:

```sql
ALTER TABLE fixtures ADD COLUMN is_past INTEGER DEFAULT 0;
```

However, note that the column values won't be automatically populated. You would need to run an UPDATE statement to set the values based on match_date.

## Testing

Run the test suite to ensure everything works correctly:

```bash
cd worker
npm test
```

All tests should pass with the new implementation computing `is_past` dynamically in the API layer.
1 change: 0 additions & 1 deletion worker/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ CREATE TABLE IF NOT EXISTS fixtures (
home_team TEXT NOT NULL,
away_team TEXT NOT NULL,
venue TEXT,
is_past INTEGER DEFAULT 0,
created_at INTEGER NOT NULL,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE
);
Expand Down
13 changes: 7 additions & 6 deletions worker/seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ INSERT INTO players (id, team_id, name, created_at) VALUES
('player-6', '00000000-0000-0000-0000-000000000000', 'Frank Foster', strftime('%s', 'now'));

-- Insert fixtures (3 future, 2 past)
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, is_past, created_at) VALUES
('fixture-future-1', '00000000-0000-0000-0000-000000000000', date('now', '+7 days'), '19:30', 'Test Team E2E', 'Future Team A', 'Home Venue', 0, strftime('%s', 'now')),
('fixture-future-2', '00000000-0000-0000-0000-000000000000', date('now', '+14 days'), '20:00', 'Future Team B', 'Test Team E2E', 'Away Venue', 0, strftime('%s', 'now')),
('fixture-future-3', '00000000-0000-0000-0000-000000000000', date('now', '+21 days'), '19:45', 'Test Team E2E', 'Future Team C', 'Home Venue', 0, strftime('%s', 'now')),
('fixture-past-1', '00000000-0000-0000-0000-000000000000', date('now', '-7 days'), '19:30', 'Past Team A', 'Test Team E2E', 'Away Venue', 1, strftime('%s', 'now')),
('fixture-past-2', '00000000-0000-0000-0000-000000000000', date('now', '-14 days'), '20:00', 'Test Team E2E', 'Past Team B', 'Home Venue', 1, strftime('%s', 'now'));
-- Note: is_past is computed dynamically by the API based on match_date
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, created_at) VALUES
('fixture-future-1', '00000000-0000-0000-0000-000000000000', date('now', '+7 days'), '19:30', 'Test Team E2E', 'Future Team A', 'Home Venue', strftime('%s', 'now')),
('fixture-future-2', '00000000-0000-0000-0000-000000000000', date('now', '+14 days'), '20:00', 'Future Team B', 'Test Team E2E', 'Away Venue', strftime('%s', 'now')),
('fixture-future-3', '00000000-0000-0000-0000-000000000000', date('now', '+21 days'), '19:45', 'Test Team E2E', 'Future Team C', 'Home Venue', strftime('%s', 'now')),
('fixture-past-1', '00000000-0000-0000-0000-000000000000', date('now', '-7 days'), '19:30', 'Past Team A', 'Test Team E2E', 'Away Venue', strftime('%s', 'now')),
('fixture-past-2', '00000000-0000-0000-0000-000000000000', date('now', '-14 days'), '20:00', 'Test Team E2E', 'Past Team B', 'Home Venue', strftime('%s', 'now'));

-- Initialize availability for all fixtures (all players available)
INSERT INTO availability (id, fixture_id, player_id, is_available, updated_at) VALUES
Expand Down
59 changes: 15 additions & 44 deletions worker/src/database.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,24 @@ describe('DatabaseService Integration Tests', () => {
expect(fixture.venue).toBe('Test Venue');
});

it('should mark past fixtures correctly', async () => {
const pastFixture = await db.createFixture(
'team-123',
'2020-01-01',
'Jan 1 Wed 19:00',
'Home Team',
'Away Team'
);

expect(pastFixture.is_past).toBe(1);
});

it('should mark future fixtures correctly', async () => {
const futureFixture = await db.createFixture(
it('should create a fixture with all required fields', async () => {
const fixture = await db.createFixture(
'team-123',
'2030-12-31',
'Dec 31 Wed 19:00',
'Home Team',
'Away Team'
'Away Team',
'Test Venue'
);

expect(futureFixture.is_past).toBe(0);
expect(fixture.id).toBeDefined();
expect(fixture.team_id).toBe('team-123');
expect(fixture.match_date).toBe('2030-12-31');
expect(fixture.day_time).toBe('Dec 31 Wed 19:00');
expect(fixture.home_team).toBe('Home Team');
expect(fixture.away_team).toBe('Away Team');
expect(fixture.venue).toBe('Test Venue');
expect(fixture.created_at).toBeDefined();
});

it('should get fixtures for a team', async () => {
Expand All @@ -133,7 +129,6 @@ describe('DatabaseService Integration Tests', () => {
home_team: 'Home Team 1',
away_team: 'Away Team 1',
venue: null,
is_past: 0,
created_at: Date.now()
},
{
Expand All @@ -144,7 +139,6 @@ describe('DatabaseService Integration Tests', () => {
home_team: 'Home Team 2',
away_team: 'Away Team 2',
venue: 'Test Venue',
is_past: 0,
created_at: Date.now()
}
];
Expand Down Expand Up @@ -370,7 +364,7 @@ describe('DatabaseService Integration Tests', () => {
);

expect(fixture.id).toBeDefined();
expect(fixture.is_past).toBe(0);
expect(fixture.match_date).toBe('2026-01-15');

// Create availability
await db.createAvailability(fixture.id, player1.id, true);
Expand All @@ -396,7 +390,6 @@ describe('DatabaseService Integration Tests', () => {
home_team: 'Home United',
away_team: 'Away City',
venue: 'Test Venue',
is_past: 0,
created_at: Date.now()
};

Expand Down Expand Up @@ -433,12 +426,11 @@ describe('DatabaseService Integration Tests', () => {
mockD1.prepare = () => ({
bind: (...params: any[]) => {
// Verify update was called with correct parameters
if (params.length === 4) {
if (params.length === 3) {
updateCalled = true;
expect(params[0]).toBe('2026-04-20'); // match_date
expect(params[1]).toBe('Apr 20 19:00'); // day_time
expect(params[2]).toBe(0); // is_past (future date)
expect(params[3]).toBe(fixtureId);
expect(params[2]).toBe(fixtureId);
}
return {
run: async () => ({ success: true })
Expand All @@ -451,26 +443,6 @@ describe('DatabaseService Integration Tests', () => {
expect(updateCalled).toBe(true);
});

it('should mark fixture as past when updating to past date', async () => {
const fixtureId = 'fixture-123';
let isPastValue: number | null = null;

mockD1.prepare = () => ({
bind: (...params: any[]) => {
if (params.length === 4) {
isPastValue = params[2]; // is_past parameter
}
return {
run: async () => ({ success: true })
};
}
});

await db.updateFixtureDate(fixtureId, '2025-01-01', 'Jan 1 20:00');

expect(isPastValue).toBe(1);
});

it('should clear availability for fixture', async () => {
const fixtureId = 'fixture-123';
let deleteCalled = false;
Expand Down Expand Up @@ -505,7 +477,6 @@ describe('DatabaseService Integration Tests', () => {
home_team: 'Home Team',
away_team: 'Away Team',
venue: null,
is_past: 0,
created_at: Date.now()
};

Expand Down
47 changes: 20 additions & 27 deletions worker/src/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Env, Team, Fixture, Player, Availability, FinalSelection } from './types';
import { generateUUID, now, isPastDate } from './utils';
import type { Env, Team, FixtureRow, Player, Availability, FinalSelection } from './types';
import { generateUUID, now } from './utils';

/**
* Database service for D1 operations
Expand Down Expand Up @@ -52,17 +52,16 @@ export class DatabaseService {
homeTeam: string,
awayTeam: string,
venue?: string
): Promise<Fixture> {
): Promise<FixtureRow> {
const id = generateUUID();
const timestamp = now();
const isPast = isPastDate(matchDate) ? 1 : 0;

await this.db
.prepare(`
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, is_past, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`)
.bind(id, teamId, matchDate, dayTime, homeTeam, awayTeam, venue || null, isPast, timestamp)
.bind(id, teamId, matchDate, dayTime, homeTeam, awayTeam, venue || null, timestamp)
.run();

return {
Expand All @@ -73,44 +72,41 @@ export class DatabaseService {
home_team: homeTeam,
away_team: awayTeam,
venue: venue || null,
is_past: isPast,
created_at: timestamp
};
}

async getFixtures(teamId: string): Promise<Fixture[]> {
async getFixtures(teamId: string): Promise<FixtureRow[]> {
const result = await this.db
.prepare('SELECT * FROM fixtures WHERE team_id = ? ORDER BY match_date ASC')
.bind(teamId)
.all<Fixture>();
.all<FixtureRow>();

return result.results || [];
}

async getFixture(fixtureId: string): Promise<Fixture | null> {
async getFixture(fixtureId: string): Promise<FixtureRow | null> {
const result = await this.db
.prepare('SELECT * FROM fixtures WHERE id = ?')
.bind(fixtureId)
.first<Fixture>();
.first<FixtureRow>();

return result;
}

async getFixtureByTeams(teamId: string, homeTeam: string, awayTeam: string): Promise<Fixture | null> {
async getFixtureByTeams(teamId: string, homeTeam: string, awayTeam: string): Promise<FixtureRow | null> {
const result = await this.db
.prepare('SELECT * FROM fixtures WHERE team_id = ? AND home_team = ? AND away_team = ?')
.bind(teamId, homeTeam, awayTeam)
.first<Fixture>();
.first<FixtureRow>();

return result;
}

async updateFixtureDate(fixtureId: string, matchDate: string, dayTime: string): Promise<void> {
const isPast = isPastDate(matchDate) ? 1 : 0;

await this.db
.prepare('UPDATE fixtures SET match_date = ?, day_time = ?, is_past = ? WHERE id = ?')
.bind(matchDate, dayTime, isPast, fixtureId)
.prepare('UPDATE fixtures SET match_date = ?, day_time = ? WHERE id = ?')
.bind(matchDate, dayTime, fixtureId)
.run();
}

Expand Down Expand Up @@ -239,14 +235,13 @@ export class DatabaseService {
dayTime: string,
playerIds: string[]
): Promise<void> {
const isPast = isPastDate(matchDate) ? 1 : 0;
const timestamp = now();

// Build batch of statements
const statements = [
// Update fixture date
this.db.prepare('UPDATE fixtures SET match_date = ?, day_time = ?, is_past = ? WHERE id = ?')
.bind(matchDate, dayTime, isPast, fixtureId),
this.db.prepare('UPDATE fixtures SET match_date = ?, day_time = ? WHERE id = ?')
.bind(matchDate, dayTime, fixtureId),
// Clear availability
this.db.prepare('DELETE FROM availability WHERE fixture_id = ?')
.bind(fixtureId),
Expand Down Expand Up @@ -276,18 +271,17 @@ export class DatabaseService {
awayTeam: string,
venue: string | undefined,
playerIds: string[]
): Promise<Fixture> {
): Promise<FixtureRow> {
const fixtureId = generateUUID();
const timestamp = now();
const isPast = isPastDate(matchDate) ? 1 : 0;

// Build batch of statements
const statements = [
// Create fixture
this.db.prepare(`
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, is_past, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).bind(fixtureId, teamId, matchDate, dayTime, homeTeam, awayTeam, venue || null, isPast, timestamp),
INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).bind(fixtureId, teamId, matchDate, dayTime, homeTeam, awayTeam, venue || null, timestamp),
];

// Add availability inserts for each player
Expand All @@ -310,7 +304,6 @@ export class DatabaseService {
home_team: homeTeam,
away_team: awayTeam,
venue: venue || null,
is_past: isPast,
created_at: timestamp
};
}
Expand Down
Loading