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:
startDateandendDateare optional; if both are provided,startDatemust be <=endDate.- You may also use
contextName,contextType,contextStartDate,contextEndDateas alternative keys. typemust 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:
startDateandendDateare optional; if both provided,startDatemust be <=endDate.- If changing
name, it must be unique. typemust beleague,tournament, ormiscellaneousif 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:
contextNameandcontextTypeare 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:
teamNameis optional if at least one player name is provided- If no
teamNameis provided, it will be auto-generated as "Team {skip's last name}" vicePositiondefaults to "third"skipPositiondefaults to "fourth"vicePositionandskipPositionmust 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.jscurl -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_dateend_datecreated_atupdated_at
id(PRIMARY KEY)team_namecontext_id(FOREIGN KEY)lead,second,third,fourthvice_positionskip_positionhome_clubcreated_atupdated_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-dbThis 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_PATHenvironment variable
MIT License - see LICENSE file for details.