From 89e1cbcbb7813d08c0d35d0cf09beab064cf0844 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 21 Feb 2026 17:17:24 -0600 Subject: [PATCH] Add CLI tools for task CRUD and file attachments --- scripts/attach-file.sh | 79 +++++++++++++++++++++++++++++ scripts/gantt-task-crud.sh | 100 +++++++++++++++++++++++++++++++++++++ scripts/view-attachment.sh | 77 ++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100755 scripts/attach-file.sh create mode 100755 scripts/gantt-task-crud.sh create mode 100755 scripts/view-attachment.sh diff --git a/scripts/attach-file.sh b/scripts/attach-file.sh new file mode 100755 index 0000000..57e3aa0 --- /dev/null +++ b/scripts/attach-file.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Attach a file to a gantt board task +# Usage: ./attach-file.sh + +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") + +TASK_ID="$1" +FILE_PATH="$2" + +if [[ -z "$TASK_ID" || -z "$FILE_PATH" ]]; then + echo "Usage: $0 " + exit 1 +fi + +if [[ ! -f "$FILE_PATH" ]]; then + echo "Error: File not found: $FILE_PATH" + exit 1 +fi + +FILENAME=$(basename "$FILE_PATH") +# Determine MIME type based on extension +EXTENSION="${FILENAME##*.}" +case "$EXTENSION" in + md|markdown) MIME_TYPE="text/markdown" ;; + txt) MIME_TYPE="text/plain" ;; + json) MIME_TYPE="application/json" ;; + pdf) MIME_TYPE="application/pdf" ;; + png) MIME_TYPE="image/png" ;; + jpg|jpeg) MIME_TYPE="image/jpeg" ;; + gif) MIME_TYPE="image/gif" ;; + *) MIME_TYPE="application/octet-stream" ;; +esac + +# Convert file to base64 data URL +BASE64_CONTENT=$(base64 -i "$FILE_PATH" | tr -d '\n') +DATA_URL="data:$MIME_TYPE;base64,$BASE64_CONTENT" + +# Generate attachment ID and timestamp +ATTACHMENT_ID=$(uuidgen | tr '[:upper:]' '[:lower:]') +NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +FILE_SIZE=$(stat -f%z "$FILE_PATH") + +# Get current task attachments +echo "Fetching current task..." +CURRENT_ATTACHMENTS=$(curl -s "$SUPABASE_URL/rest/v1/tasks?id=eq.$TASK_ID&select=attachments" "${HEADERS[@]}" | jq 'if type == "array" then .[0].attachments // [] else .attachments // [] end') + +# Create new attachment object (field names must match UI expectations) +NEW_ATTACHMENT=$(jq -n \ + --arg id "$ATTACHMENT_ID" \ + --arg name "$FILENAME" \ + --arg type "$MIME_TYPE" \ + --argjson size "$FILE_SIZE" \ + --arg dataUrl "$DATA_URL" \ + --arg uploadedAt "$NOW" \ + '{ + id: $id, + name: $name, + type: $type, + size: $size, + dataUrl: $dataUrl, + uploadedAt: $uploadedAt + }') + +# Merge with existing attachments +UPDATED_ATTACHMENTS=$(echo "$CURRENT_ATTACHMENTS" | jq --argjson new "$NEW_ATTACHMENT" '. + [$new]') + +# Update task +echo "Attaching file to task..." +curl -s -X PATCH "$SUPABASE_URL/rest/v1/tasks?id=eq.$TASK_ID" \ + "${HEADERS[@]}" \ + -d "{\"attachments\": $UPDATED_ATTACHMENTS, \"updated_at\": \"$NOW\"}" | jq '.' + +echo "" +echo "✅ Attached: $FILENAME" +echo " Size: $FILE_SIZE bytes" +echo " Type: $MIME_TYPE" diff --git a/scripts/gantt-task-crud.sh b/scripts/gantt-task-crud.sh new file mode 100755 index 0000000..4065e80 --- /dev/null +++ b/scripts/gantt-task-crud.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# Gantt Board Task CRUD Operations +# Usage: ./task-crud.sh [create|read|update|delete|list] [args...] + +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") + +function list_tasks() { + local status_filter="${1:-}" + local url="$SUPABASE_URL/rest/v1/tasks?select=*&order=created_at.desc" + if [[ -n "$status_filter" ]]; then + url="$SUPABASE_URL/rest/v1/tasks?select=*&status=eq.$status_filter&order=created_at.desc" + fi + curl -s "$url" "${HEADERS[@]}" | jq '.' +} + +function get_task() { + local task_id="$1" + curl -s "$SUPABASE_URL/rest/v1/tasks?id=eq.$task_id&select=*" "${HEADERS[@]}" | jq '.[0]' +} + +function create_task() { + local title="$1" + local status="${2:-open}" + local priority="${3:-medium}" + local project_id="${4:-1}" + local assignee_id="${5:-9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa}" + + local uuid=$(uuidgen | tr '[:upper:]' '[:lower:]') + local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + curl -s -X POST "$SUPABASE_URL/rest/v1/tasks" \ + "${HEADERS[@]}" \ + -d "{ + \"id\": \"$uuid\", + \"title\": \"$title\", + \"status\": \"$status\", + \"priority\": \"$priority\", + \"project_id\": \"$project_id\", + \"assignee_id\": \"$assignee_id\", + \"created_at\": \"$now\", + \"updated_at\": \"$now\", + \"comments\": [], + \"tags\": [], + \"attachments\": [] + }" | jq '.' + + echo "Created task: $uuid" +} + +function update_task() { + local task_id="$1" + local field="$2" + local value="$3" + local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + curl -s -X PATCH "$SUPABASE_URL/rest/v1/tasks?id=eq.$task_id" \ + "${HEADERS[@]}" \ + -d "{\"$field\": \"$value\", \"updated_at\": \"$now\"}" | jq '.' + + echo "Updated task $task_id: $field = $value" +} + +function delete_task() { + local task_id="$1" + curl -s -X DELETE "$SUPABASE_URL/rest/v1/tasks?id=eq.$task_id" "${HEADERS[@]}" + echo "Deleted task: $task_id" +} + +# Main +case "$1" in + list) + list_tasks "$2" + ;; + get) + get_task "$2" + ;; + create) + create_task "$2" "$3" "$4" "$5" "$6" + ;; + update) + update_task "$2" "$3" "$4" + ;; + delete) + delete_task "$2" + ;; + *) + echo "Usage: $0 [list|get|create|update|delete] [args...]" + echo "" + echo "Examples:" + echo " $0 list # List all tasks" + echo " $0 list open # List open tasks" + echo " $0 get # Get specific task" + echo " $0 create \"Task title\" open medium # Create task" + echo " $0 update status done # Update task status" + echo " $0 delete # Delete task" + ;; +esac \ No newline at end of file diff --git a/scripts/view-attachment.sh b/scripts/view-attachment.sh new file mode 100755 index 0000000..ded812e --- /dev/null +++ b/scripts/view-attachment.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# View attachment from a gantt board task +# Usage: ./view-attachment.sh [attachment-index] + +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") + +TASK_ID="$1" +ATTACHMENT_INDEX="${2:-0}" + +if [[ -z "$TASK_ID" ]]; then + echo "Usage: $0 [attachment-index]" + echo "" + echo "Examples:" + echo " $0 33ebc71e-7d40-456c-8f98-bb3578d2bb2b # View first attachment" + echo " $0 33ebc71e-7d40-456c-8f98-bb3578d2bb2b 0 # View first attachment (explicit)" + exit 1 +fi + +# Fetch task with attachments +echo "Fetching task attachments..." +TASK_DATA=$(curl -s "$SUPABASE_URL/rest/v1/tasks?id=eq.$TASK_ID&select=title,attachments" "${HEADERS[@]}") + +# Extract attachments array +ATTACHMENTS=$(echo "$TASK_DATA" | jq -r 'if type == "array" then (.[0].attachments // []) else (.attachments // []) end') +ATTACHMENT_COUNT=$(echo "$ATTACHMENTS" | jq 'length') + +if [[ "$ATTACHMENT_COUNT" -eq 0 ]]; then + echo "❌ No attachments found on this task." + exit 1 +fi + +echo "" +echo "Found $ATTACHMENT_COUNT attachment(s):" +echo "$ATTACHMENTS" | jq -r '.[] | " - \(.name) (\(.size) bytes, \(.type))"' +echo "" + +# Get the requested attachment +ATTACHMENT=$(echo "$ATTACHMENTS" | jq -r ".[$ATTACHMENT_INDEX]") +if [[ "$ATTACHMENT" == "null" ]]; then + echo "❌ Attachment index $ATTACHMENT_INDEX not found." + exit 1 +fi + +FILENAME=$(echo "$ATTACHMENT" | jq -r '.name') +MIME_TYPE=$(echo "$ATTACHMENT" | jq -r '.type') +DATA_URL=$(echo "$ATTACHMENT" | jq -r '.url') + +# Extract base64 content from data URL (remove data:mime/type;base64, prefix) +BASE64_CONTENT=$(echo "$DATA_URL" | sed 's/^data:[^;]*;base64,//') + +# Decode to temp file +TEMP_FILE="/tmp/gantt-attachment-$(date +%s)-$FILENAME" +echo "$BASE64_CONTENT" | base64 -d > "$TEMP_FILE" + +echo "📎 Attachment: $FILENAME" +echo "📋 Type: $MIME_TYPE" +echo "📊 Size: $(stat -f%z "$TEMP_FILE") bytes" +echo "" +echo "--- CONTENT ---" +echo "" + +# Display based on type +if [[ "$MIME_TYPE" == text/* ]] || [[ "$FILENAME" == *.md ]] || [[ "$FILENAME" == *.txt ]]; then + cat "$TEMP_FILE" +else + echo "(Binary file - saved to: $TEMP_FILE)" + echo "Open with: open '$TEMP_FILE'" +fi + +echo "" +echo "---------------" + +# Clean up temp file +rm -f "$TEMP_FILE"