A microservice for managing curling teams and their contexts (leagues/tournaments) with SQLite storage.
- Team Management: Create, read, update, and delete curling teams
- Context Management: Teams belong to contexts (leagues or tournaments)
- Smart Search: Intelligent search with relevance scoring
- Bulk Operations: Import multiple teams using format strings
- SQLite Storage: Local database with automatic schema creation
Each curling team has:
- Team Name: Required (or auto-generated from skip's last name)
- Players: Lead, Second, Third, Fourth (all optional)
- Vice: One of the 4 players (defaults to Third)
- Skip: One of the 4 players (defaults to Fourth)
- Home Club: Optional string
- Context: Required - the event/league the team participates in
-
Install dependencies:
npm install
-
Set environment variables:
# Required: Path to SQLite database file DB_PATH=./data/curling.db # Optional: Server port (defaults to 3000) PORT=3000
-
Run the service:
# Development mode npm run dev # Production mode npm run build npm start
Get all contexts (leagues and tournaments).
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "Tuesday League",
"type": "league",
"startDate": "2024-01-15T18:00:00Z",
"endDate": "2024-03-15T22:00:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
]
}
Create a new context without any teams.
Request Body:
{
"name": "Autumn League",
"type": "league",
"startDate": "2024-09-01T18:00:00Z",
"endDate": "2024-11-30T22:00:00Z"
}
Notes:
startDate
andendDate
are optional; if both are provided,startDate
must be <=endDate
.- You may also use
contextName
,contextType
,contextStartDate
,contextEndDate
as alternative keys. type
must beleague
,tournament
, ormiscellaneous
.
Example:
curl -X POST http://localhost:3000/api/contexts \
-H "Content-Type: application/json" \
-d '{
"name": "Autumn League",
"type": "league"
}'
Update context name, type, or dates.
Request Body (all optional):
{
"name": "Autumn League - Division A",
"type": "league",
"startDate": "2024-09-01T18:00:00Z",
"endDate": "2024-12-01T22:00:00Z"
}
Rules:
startDate
andendDate
are optional; if both provided,startDate
must be <=endDate
.- If changing
name
, it must be unique. type
must beleague
,tournament
, ormiscellaneous
if provided.
Example:
curl -X PUT "http://localhost:3000/api/contexts/Autumn%20League" \
-H "Content-Type: application/json" \
-d '{
"endDate": "2024-12-01T22:00:00Z"
}'
Delete a context. Associated teams will also be deleted (cascade).
Example:
curl -X DELETE "http://localhost:3000/api/contexts/Autumn%20League"
Get all teams for a specific context.
Example: GET /api/teams/Tuesday%20League
Get a specific team.
Example: GET /api/teams/Tuesday%20League/Team%20Smith
Create a new team.
Notes:
contextName
andcontextType
are required.- Context dates are optional in this request; if provided, both must form a valid range.
Request Body:
{
"teamName": "Team Smith",
"contextName": "Tuesday League",
"contextType": "league",
"contextStartDate": "2024-01-15T18:00:00Z",
"contextEndDate": "2024-03-15T22:00:00Z",
"lead": "John Doe",
"second": "Jane Smith",
"third": "Bob Johnson",
"fourth": "Alice Brown",
"vicePosition": "third",
"skipPosition": "fourth",
"homeClub": "Downtown Curling Club"
}
Notes:
teamName
is optional if at least one player name is provided- If no
teamName
is provided, it will be auto-generated as "Team {skip's last name}" vicePosition
defaults to "third"skipPosition
defaults to "fourth"vicePosition
andskipPosition
must be different
Update a team.
Request Body (all fields optional):
{
"teamName": "New Team Name",
"lead": "New Lead Player",
"homeClub": "New Club"
}
Delete a team.
Search for teams within a context using fuzzy matching.
Example: GET /api/search?contextName=Tuesday%20League&q=oreilly
Fuzzy Search Features:
- Fuzzy Matching: Handles typos, partial matches, and variations
- Smart Relevance: Prioritizes matches based on field importance
- Multi-field Search: Searches across all team and player fields
Search Relevance Order:
- Team names (highest priority)
- Skip's last name
- Skip's first name
- Any player name
- Home club
- Any field containing query (lowest priority)
Examples of Fuzzy Matching:
"oreilly"
matches"Kenneth O'Reilly"
"macdonald"
matches"Michael MacDonald"
"vanderberg"
matches"Pieter van der Berg"
"ken"
matches"Kenneth"
"downtown"
matches"Downtown Curling Club"
Response:
{
"success": true,
"data": [
{
"team": { /* team object */ },
"context": { /* context object */ },
"matchType": "skipLastName",
"matchField": "skipLastName",
"relevance": 90
}
]
}
Bulk create teams using a format string.
Request Body:
{
"format": "teamName, lead, second, third, fourth, homeClub",
"data": [
["Team Alpha", "Alice Smith", "Bob Jones", "Carol White", "David Brown", "Downtown Club"],
["Team Beta", "Eve Wilson", "Frank Davis", "Grace Lee", "Henry Taylor", "Uptown Club"]
],
"contextName": "Spring Tournament",
"contextType": "tournament",
"contextStartDate": "2024-04-01T09:00:00Z",
"contextEndDate": "2024-04-03T18:00:00Z"
}
Format String Fields:
teamName
: Team namelead
,second
,third
,fourth
: Player namesvicePosition
,skipPosition
: Player positionshomeClub
: Home curling club
curl -X POST http://localhost:3000/api/teams \
-H "Content-Type: application/json" \
-d '{
"contextName": "Tuesday League",
"contextType": "league",
"contextStartDate": "2024-01-15T18:00:00Z",
"contextEndDate": "2024-03-15T22:00:00Z",
"lead": "John Doe",
"second": "Jane Smith",
"third": "Bob Johnson",
"fourth": "Alice Brown",
"homeClub": "Downtown Curling Club"
}'
This will create a team named "Team Brown" (from Alice Brown's last name).
# Basic search
curl "http://localhost:3000/api/search?contextName=Tuesday%20League&q=smith"
# Fuzzy search examples
curl "http://localhost:3000/api/search?contextName=Tuesday%20League&q=oreilly"
curl "http://localhost:3000/api/search?contextName=Tuesday%20League&q=macdonald"
curl "http://localhost:3000/api/search?contextName=Tuesday%20League&q=ken"
# Test the fuzzy search functionality
node tests/test-fuzzy-search.js
curl -X POST http://localhost:3000/api/teams/bulk \
-H "Content-Type: application/json" \
-d '{
"format": "teamName, lead, second, third, fourth, homeClub",
"data": [
["Team Alpha", "Alice Smith", "Bob Jones", "Carol White", "David Brown", "Downtown Club"],
["Team Beta", "Eve Wilson", "Frank Davis", "Grace Lee", "Henry Taylor", "Uptown Club"]
],
"contextName": "Spring Tournament",
"contextType": "tournament",
"contextStartDate": "2024-04-01T09:00:00Z",
"contextEndDate": "2024-04-03T18:00:00Z"
}'
The service automatically creates the following tables:
id
(PRIMARY KEY)name
(UNIQUE)type
(league/tournament)start_date
end_date
created_at
updated_at
id
(PRIMARY KEY)team_name
context_id
(FOREIGN KEY)lead
,second
,third
,fourth
vice_position
skip_position
home_club
created_at
updated_at
- UNIQUE(team_name, context_id)
All API endpoints return consistent error responses:
{
"success": false,
"error": "Error message describing what went wrong"
}
Common error scenarios:
- Missing required fields
- Invalid vice/skip positions (must be different)
- Team not found
- Context not found
- Database connection issues
npm run setup
: Initialize project (creates data directory and .env file)npm run dev
: Start development server with hot reloadnpm run build
: Build TypeScript to JavaScriptnpm start
: Start production servernpm run lint
: Run ESLintnpm run lint:fix
: Fix ESLint issuesnpm run test
: Run API testsnpm run clear-db
: Clear all data from the database
DB_PATH
: Path to SQLite database file (required)PORT
: Server port (default: 3000)
The service uses SQLite for data storage. The database file is automatically created when the server starts.
Clear Database:
npm run clear-db
This will:
- Drop all existing tables and data
- Tables will be automatically recreated when the server restarts
- Useful for development and testing
Database Location:
- Default:
./data/curling.db
(as specified in .env) - Can be changed by modifying the
DB_PATH
environment variable
MIT License - see LICENSE file for details.