gantt-board/scripts/sprint.sh

512 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#
# Sprint CLI for Gantt Board
# CRUD operations for sprints
# Usage: ./sprint.sh [create|list|get|update|delete|close] [options]
set -e
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SUPABASE_URL="https://qnatchrjlpehiijwtreh.supabase.co"
SERVICE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuYXRjaHJqbHBlaGlpand0cmVoIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MTY0MDQzNiwiZXhwIjoyMDg3MjE2NDM2fQ.rHoc3NfL59S4lejU4-ArSzox1krQkQG-TnfXb6sslm0"
HEADERS=(-H "apikey: $SERVICE_KEY" -H "Authorization: Bearer $SERVICE_KEY" -H "Content-Type: application/json")
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Helper Functions
log_info() { echo -e "${BLUE}${NC} $1"; }
log_success() { echo -e "${GREEN}${NC} $1"; }
log_warning() { echo -e "${YELLOW}${NC} $1"; }
log_error() { echo -e "${RED}${NC} $1"; }
# Check dependencies
check_dependencies() {
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed. Install with: brew install jq"
exit 1
fi
if ! command -v uuidgen &> /dev/null; then
log_error "uuidgen is required but not installed."
exit 1
fi
}
# Generate UUID
generate_uuid() {
uuidgen | tr '[:upper:]' '[:lower:]'
}
# Get current timestamp ISO format
get_timestamp() {
date -u +"%Y-%m-%dT%H:%M:%SZ"
}
# Show usage
show_usage() {
cat << EOF
Sprint CLI for Gantt Board
USAGE:
./sprint.sh [COMMAND] [OPTIONS]
COMMANDS:
create Create a new sprint
list List all sprints
get <id> Get a specific sprint by ID or name
update <id> Update a sprint
delete <id> Delete a sprint
close <id> Close/complete a sprint
CREATE OPTIONS:
--name "Name" Sprint name (required)
--project "Project" Project name or ID (required)
--goal "Goal" Sprint goal/description
--start-date "YYYY-MM-DD" Start date
--end-date "YYYY-MM-DD" End date
--status [planning|active|closed|completed] Status (default: planning)
LIST OPTIONS:
--status <status> Filter by status
--project <project> Filter by project name/ID
--active Show only active sprints
--json Output as JSON
UPDATE OPTIONS:
--name "Name"
--goal "Goal"
--start-date "YYYY-MM-DD"
--end-date "YYYY-MM-DD"
--status <status>
EXAMPLES:
# Create sprint
./sprint.sh create --name "Sprint 1" --project "Web Projects" \
--goal "Complete MVP features" --start-date 2026-02-24 --end-date 2026-03-07
# List active sprints
./sprint.sh list --active
# Get sprint by name
./sprint.sh get "Sprint 1"
# Close a sprint (marks as completed)
./sprint.sh close "Sprint 1"
# Delete sprint
./sprint.sh delete <sprint-id>
EOF
}
# Resolve project name to ID
resolve_project_id() {
local identifier="$1"
# Check if it's already a UUID
if [[ "$identifier" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
echo "$identifier"
return 0
fi
# Search by name (case-insensitive)
local project_id
project_id=$(curl -s "${SUPABASE_URL}/rest/v1/projects?name=ilike.*${identifier}*&select=id" \
"${HEADERS[@]}" | jq -r '.[0].id // empty')
if [[ -n "$project_id" ]]; then
echo "$project_id"
return 0
fi
# Try exact match
local encoded_name
encoded_name=$(printf '%s' "$identifier" | jq -sRr @uri)
project_id=$(curl -s "${SUPABASE_URL}/rest/v1/projects?name=eq.${encoded_name}&select=id" \
"${HEADERS[@]}" | jq -r '.[0].id // empty')
if [[ -n "$project_id" ]]; then
echo "$project_id"
return 0
fi
log_error "Project '$identifier' not found"
return 1
}
# Resolve sprint identifier to ID
resolve_sprint_id() {
local identifier="$1"
# Check if it's already a UUID
if [[ "$identifier" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
echo "$identifier"
return 0
fi
# Search by name (case-insensitive)
local sprint_id
sprint_id=$(curl -s "${SUPABASE_URL}/rest/v1/sprints?name=ilike.*${identifier}*&select=id" \
"${HEADERS[@]}" | jq -r '.[0].id // empty')
if [[ -n "$sprint_id" ]]; then
echo "$sprint_id"
return 0
fi
# Try exact match
local encoded_name
encoded_name=$(printf '%s' "$identifier" | jq -sRr @uri)
sprint_id=$(curl -s "${SUPABASE_URL}/rest/v1/sprints?name=eq.${encoded_name}&select=id" \
"${HEADERS[@]}" | jq -r '.[0].id // empty')
if [[ -n "$sprint_id" ]]; then
echo "$sprint_id"
return 0
fi
log_error "Sprint '$identifier' not found"
return 1
}
# Create command
cmd_create() {
local name=""
local project=""
local goal=""
local start_date=""
local end_date=""
local status="planning"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--name) name="$2"; shift 2 ;;
--project) project="$2"; shift 2 ;;
--goal) goal="$2"; shift 2 ;;
--start-date) start_date="$2"; shift 2 ;;
--end-date) end_date="$2"; shift 2 ;;
--status) status="$2"; shift 2 ;;
*) shift ;;
esac
done
# Validate required fields
if [[ -z "$name" ]]; then
log_error "Name is required (use --name)"
exit 1
fi
if [[ -z "$project" ]]; then
log_error "Project is required (use --project)"
exit 1
fi
# Resolve project
local project_id
project_id=$(resolve_project_id "$project")
# Generate sprint data
local sprint_id
sprint_id=$(generate_uuid)
local timestamp
timestamp=$(get_timestamp)
# Build JSON payload
local json_payload
json_payload=$(jq -n \
--arg id "$sprint_id" \
--arg name "$name" \
--arg project_id "$project_id" \
--arg status "$status" \
--arg created_at "$timestamp" \
'{
id: $id,
name: $name,
project_id: $project_id,
status: $status,
created_at: $created_at
}')
# Add optional fields
if [[ -n "$goal" ]]; then
json_payload=$(echo "$json_payload" | jq --arg v "$goal" '. + {goal: $v}')
fi
if [[ -n "$start_date" ]]; then
json_payload=$(echo "$json_payload" | jq --arg v "$start_date" '. + {start_date: $v}')
fi
if [[ -n "$end_date" ]]; then
json_payload=$(echo "$json_payload" | jq --arg v "$end_date" '. + {end_date: $v}')
fi
# Create sprint
log_info "Creating sprint..."
local response
response=$(curl -s -X POST "${SUPABASE_URL}/rest/v1/sprints" \
"${HEADERS[@]}" \
-d "$json_payload")
# Supabase returns empty on success, or error JSON on failure
if [[ -z "$response" ]]; then
log_success "Sprint created: $sprint_id"
elif [[ "$response" == *"error"* ]] || [[ "$response" == *'"code"'* ]]; then
log_error "Failed to create sprint"
echo "$response" | jq . 2>/dev/null || echo "$response"
exit 1
else
log_success "Sprint created: $sprint_id"
echo "$response" | jq . 2>/dev/null || echo "$response"
fi
}
# List command
cmd_list() {
local filter_status=""
local filter_project=""
local active_only=false
local output_json=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--status) filter_status="$2"; shift 2 ;;
--project) filter_project="$2"; shift 2 ;;
--active) active_only=true; shift ;;
--json) output_json=true; shift ;;
*) shift ;;
esac
done
# Build query
local query="${SUPABASE_URL}/rest/v1/sprints?select=*,projects(name)&order=created_at.desc"
if [[ "$active_only" == true ]]; then
filter_status="active"
fi
if [[ -n "$filter_status" ]]; then
local encoded_status
encoded_status=$(printf '%s' "$filter_status" | jq -sRr @uri)
query="${query}&status=eq.${encoded_status}"
fi
if [[ -n "$filter_project" ]]; then
if [[ ! "$filter_project" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
filter_project=$(resolve_project_id "$filter_project")
fi
query="${query}&project_id=eq.${filter_project}"
fi
log_info "Fetching sprints..."
local response
response=$(curl -s "$query" "${HEADERS[@]}")
if [[ "$output_json" == true ]]; then
echo "$response" | jq .
else
# Table output
local count
count=$(echo "$response" | jq 'length')
log_success "Found $count sprint(s)"
# Print header
printf "%-36s %-25s %-12s %-10s %-12s %-12s\n" "ID" "NAME" "PROJECT" "STATUS" "START" "END"
printf "%-36s %-25s %-12s %-10s %-12s %-12s\n" "------------------------------------" "-------------------------" "------------" "----------" "------------" "------------"
# Print rows
echo "$response" | jq -r '.[] | [.id, (.name | tostring | .[0:23]), (.projects.name // "N/A" | tostring | .[0:10]), .status, (.start_date // "N/A"), (.end_date // "N/A")] | @tsv' | while IFS=$'\t' read -r id name project status start end; do
printf "%-36s %-25s %-12s %-10s %-12s %-12s\n" "$id" "$name" "$project" "$status" "$start" "$end"
done
fi
}
# Get command
cmd_get() {
local identifier="$1"
if [[ -z "$identifier" ]]; then
log_error "Sprint ID or name required. Usage: ./sprint.sh get <id-or-name>"
exit 1
fi
local sprint_id
sprint_id=$(resolve_sprint_id "$identifier")
log_info "Fetching sprint $sprint_id..."
local response
response=$(curl -s "${SUPABASE_URL}/rest/v1/sprints?id=eq.${sprint_id}&select=*,projects(name)" \
"${HEADERS[@]}")
local sprint
sprint=$(echo "$response" | jq '.[0] // empty')
if [[ -n "$sprint" && "$sprint" != "{}" && "$sprint" != "null" ]]; then
echo "$sprint" | jq .
else
log_error "Sprint not found: $identifier"
exit 1
fi
}
# Update command
cmd_update() {
local identifier="$1"
shift
if [[ -z "$identifier" ]]; then
log_error "Sprint ID or name required. Usage: ./sprint.sh update <id-or-name> [options]"
exit 1
fi
local sprint_id
sprint_id=$(resolve_sprint_id "$identifier")
# Build update payload
local update_fields="{}"
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--name) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {name: $v}'); shift 2 ;;
--goal) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {goal: $v}'); shift 2 ;;
--start-date) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {start_date: $v}'); shift 2 ;;
--end-date) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {end_date: $v}'); shift 2 ;;
--status) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {status: $v}'); shift 2 ;;
*) shift ;;
esac
done
# Check if we have anything to update
if [[ "$update_fields" == "{}" ]]; then
log_warning "No update fields specified"
exit 0
fi
# Add updated_at timestamp
local timestamp
timestamp=$(get_timestamp)
update_fields=$(echo "$update_fields" | jq --arg t "$timestamp" '. + {updated_at: $t}')
log_info "Updating sprint $sprint_id..."
local response
response=$(curl -s -X PATCH "${SUPABASE_URL}/rest/v1/sprints?id=eq.${sprint_id}" \
"${HEADERS[@]}" \
-d "$update_fields")
if [[ -z "$response" || "$response" == "[]" || ! "$response" == *"error"* ]]; then
log_success "Sprint updated: $sprint_id"
else
log_error "Failed to update sprint"
echo "$response" | jq . 2>/dev/null || echo "$response"
exit 1
fi
}
# Close command - marks sprint as completed
cmd_close() {
local identifier="$1"
if [[ -z "$identifier" ]]; then
log_error "Sprint ID or name required. Usage: ./sprint.sh close <id-or-name>"
exit 1
fi
local sprint_id
sprint_id=$(resolve_sprint_id "$identifier")
log_info "Closing sprint $sprint_id..."
local timestamp
timestamp=$(get_timestamp)
local update_fields
update_fields=$(jq -n \
--arg status "completed" \
--arg closed_at "$timestamp" \
--arg updated_at "$timestamp" \
'{status: $status, closed_at: $closed_at, updated_at: $updated_at}')
local response
response=$(curl -s -X PATCH "${SUPABASE_URL}/rest/v1/sprints?id=eq.${sprint_id}" \
"${HEADERS[@]}" \
-d "$update_fields")
if [[ -z "$response" || "$response" == "[]" || ! "$response" == *"error"* ]]; then
log_success "Sprint closed: $sprint_id"
else
log_error "Failed to close sprint"
echo "$response" | jq . 2>/dev/null || echo "$response"
exit 1
fi
}
# Delete command
cmd_delete() {
local identifier="$1"
if [[ -z "$identifier" ]]; then
log_error "Sprint ID or name required. Usage: ./sprint.sh delete <id-or-name>"
exit 1
fi
local sprint_id
sprint_id=$(resolve_sprint_id "$identifier")
log_info "Deleting sprint $sprint_id..."
local response
response=$(curl -s -X DELETE "${SUPABASE_URL}/rest/v1/sprints?id=eq.${sprint_id}" \
"${HEADERS[@]}")
if [[ -z "$response" || "$response" == "[]" ]]; then
log_success "Sprint deleted: $sprint_id"
else
log_error "Failed to delete sprint"
echo "$response" | jq . 2>/dev/null || echo "$response"
exit 1
fi
}
# Main execution
check_dependencies
# Parse command
COMMAND="${1:-}"
shift || true
case "$COMMAND" in
create)
cmd_create "$@"
;;
list)
cmd_list "$@"
;;
get)
cmd_get "$@"
;;
update)
cmd_update "$@"
;;
close)
cmd_close "$@"
;;
delete)
cmd_delete "$@"
;;
help|--help|-h)
show_usage
;;
"")
show_usage
;;
*)
log_error "Unknown command: $COMMAND"
show_usage
exit 1
;;
esac