#!/bin/bash # # Project CLI for Gantt Board # CRUD operations for projects # Usage: ./project.sh [create|list|get|update|delete] [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 Project CLI for Gantt Board USAGE: ./project.sh [COMMAND] [OPTIONS] COMMANDS: create Create a new project list List all projects get Get a specific project by ID or name update Update a project delete Delete a project CREATE OPTIONS: --name "Name" Project name (required) --description "Description" Project description --color "#hexcolor" Project color for UI LIST OPTIONS: --json Output as JSON UPDATE OPTIONS: --name "Name" --description "Description" --color "#hexcolor" EXAMPLES: # Create project ./project.sh create --name "Web Projects" --description "All web development work" # List all active projects ./project.sh list # Get project by ID ./project.sh get a1b2c3d4-0001-0000-0000-000000000001 # Update project ./project.sh update a1b2c3d4-0001-0000-0000-000000000001 --status archived # Delete project ./project.sh delete a1b2c3d4-0001-0000-0000-000000000001 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 } # Create command cmd_create() { local name="" local description="" local color="#3b82f6" # Parse arguments while [[ $# -gt 0 ]]; do case "${1:-}" in --name) name="$2"; shift 2 ;; --description) description="$2"; shift 2 ;; --color) color="$2"; shift 2 ;; *) shift ;; esac done # Validate required fields if [[ -z "$name" ]]; then log_error "Name is required (use --name)" exit 1 fi # Generate project data local project_id project_id=$(generate_uuid) local timestamp timestamp=$(get_timestamp) # Build JSON payload local json_payload json_payload=$(jq -n \ --arg id "$project_id" \ --arg name "$name" \ --arg color "$color" \ --arg created_at "$timestamp" \ '{ id: $id, name: $name, color: $color, created_at: $created_at }') # Add optional fields if [[ -n "$description" ]]; then json_payload=$(echo "$json_payload" | jq --arg v "$description" '. + {description: $v}') fi # Create project log_info "Creating project..." local response response=$(curl -s -X POST "${SUPABASE_URL}/rest/v1/projects" \ "${HEADERS[@]}" \ -d "$json_payload") # Supabase returns empty on success, or error JSON on failure if [[ -z "$response" ]]; then log_success "Project created: $project_id" elif [[ "$response" == *"error"* ]] || [[ "$response" == *""*"code"* ]]; then log_error "Failed to create project" echo "$response" | jq . 2>/dev/null || echo "$response" exit 1 else log_success "Project created: $project_id" echo "$response" | jq . 2>/dev/null || echo "$response" fi } # List command cmd_list() { local output_json=false # Parse arguments while [[ $# -gt 0 ]]; do case "${1:-}" in --json) output_json=true; shift ;; *) shift ;; esac done # Build query local query="${SUPABASE_URL}/rest/v1/projects?select=*&order=created_at.desc" log_info "Fetching projects..." 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 project(s)" # Print header printf "%-36s %-25s %-30s\n" "ID" "NAME" "DESCRIPTION" printf "%-36s %-25s %-30s\n" "------------------------------------" "-------------------------" "------------------------------" # Print rows echo "$response" | jq -r '.[] | [.id, (.name | tostring | .[0:23]), (.description // "" | tostring | .[0:28])] | @tsv' | while IFS=$'\t' read -r id name desc; do printf "%-36s %-25s %-30s\n" "$id" "$name" "$desc" done fi } # Get command cmd_get() { local identifier="$1" if [[ -z "$identifier" ]]; then log_error "Project ID or name required. Usage: ./project.sh get " exit 1 fi local project_id project_id=$(resolve_project_id "$identifier") log_info "Fetching project $project_id..." local response response=$(curl -s "${SUPABASE_URL}/rest/v1/projects?id=eq.${project_id}&select=*" \ "${HEADERS[@]}") local project project=$(echo "$response" | jq '.[0] // empty') if [[ -n "$project" && "$project" != "{}" && "$project" != "null" ]]; then echo "$project" | jq . else log_error "Project not found: $identifier" exit 1 fi } # Update command cmd_update() { local identifier="$1" shift if [[ -z "$identifier" ]]; then log_error "Project ID or name required. Usage: ./project.sh update [options]" exit 1 fi local project_id project_id=$(resolve_project_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 ;; --description) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {description: $v}'); shift 2 ;; --color) update_fields=$(echo "$update_fields" | jq --arg v "$2" '. + {color: $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 project $project_id..." local response response=$(curl -s -X PATCH "${SUPABASE_URL}/rest/v1/projects?id=eq.${project_id}" \ "${HEADERS[@]}" \ -d "$update_fields") if [[ -z "$response" || "$response" == "[]" || ! "$response" == *"error"* ]]; then log_success "Project updated: $project_id" else log_error "Failed to update project" echo "$response" | jq . 2>/dev/null || echo "$response" exit 1 fi } # Delete command cmd_delete() { local identifier="$1" if [[ -z "$identifier" ]]; then log_error "Project ID or name required. Usage: ./project.sh delete " exit 1 fi local project_id project_id=$(resolve_project_id "$identifier") log_info "Deleting project $project_id..." local response response=$(curl -s -X DELETE "${SUPABASE_URL}/rest/v1/projects?id=eq.${project_id}" \ "${HEADERS[@]}") if [[ -z "$response" || "$response" == "[]" ]]; then log_success "Project deleted: $project_id" else log_error "Failed to delete project" 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 "$@" ;; delete) cmd_delete "$@" ;; help|--help|-h) show_usage ;; "") show_usage ;; *) log_error "Unknown command: $COMMAND" show_usage exit 1 ;; esac