From cb404432ee1c27a012831e0eba4b4a498d98d0e9 Mon Sep 17 00:00:00 2001 From: DIVYANSH-675 Date: Wed, 25 Mar 2026 01:21:46 +0530 Subject: [PATCH] Initial commit from ux_aura_assistant --- .gitea/workflows/feature_branch_autopr.yml | 128 +++++++ .gitignore | 24 ++ App.tsx | 12 + FIXED_cloudflare_build.yml | 330 +++++++++++++++++++ README.md | 44 +++ References/api_storage.json | 1 + References/manager_api.json | 1 + components/Dashboard.tsx | 106 ++++++ components/Layout.tsx | 43 +++ components/SetupPrompt.tsx | 36 ++ components/icons.tsx | 172 ++++++++++ containers/MainContainer.tsx | 44 +++ containers/layout/MainLayout.tsx | 49 +++ containers/layout/components/Footer.tsx | 15 + containers/layout/components/Header.tsx | 47 +++ content.json | 59 ++++ contexts/AuthContext.tsx | 51 +++ contexts/ThemeContext.tsx | 42 +++ craft.txt | 0 features/workspace/WorkspaceContainer.tsx | 49 +++ features/workspace/WorkspaceView.tsx | 36 ++ features/workspace/components/StatusCard.tsx | 22 ++ features/workspace/components/StatusGrid.tsx | 31 ++ hooks/useAuth.ts | 7 + hooks/useAuthSession.ts | 62 ++++ hooks/useLocalStorage.ts | 31 ++ index.html | 57 ++++ index.tsx | 22 ++ instructions.md | 43 +++ local_cookie.json | 4 + metadata.json | 8 + package.json | 27 ++ public/version.json | 1 + rules.md | 35 ++ services/apiService.ts | 160 +++++++++ services/apiUtils.ts | 87 +++++ services/appBuilder/componentService.ts | 122 +++++++ services/appBuilder/config.ts | 21 ++ services/appBuilder/contentService.ts | 1 + services/appBuilder/promptService.ts | 84 +++++ services/appBuilder/rbacService.ts | 31 ++ services/appBuilderService.ts | 8 + services/geminiService.ts | 54 +++ services/llmService.ts | 89 +++++ tsconfig.json | 29 ++ types.ts | 173 ++++++++++ vite.config.ts | 28 ++ wrangler.json | 4 + 48 files changed, 2530 insertions(+) create mode 100644 .gitea/workflows/feature_branch_autopr.yml create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 FIXED_cloudflare_build.yml create mode 100644 README.md create mode 100644 References/api_storage.json create mode 100644 References/manager_api.json create mode 100644 components/Dashboard.tsx create mode 100644 components/Layout.tsx create mode 100644 components/SetupPrompt.tsx create mode 100644 components/icons.tsx create mode 100644 containers/MainContainer.tsx create mode 100644 containers/layout/MainLayout.tsx create mode 100644 containers/layout/components/Footer.tsx create mode 100644 containers/layout/components/Header.tsx create mode 100644 content.json create mode 100644 contexts/AuthContext.tsx create mode 100644 contexts/ThemeContext.tsx create mode 100644 craft.txt create mode 100644 features/workspace/WorkspaceContainer.tsx create mode 100644 features/workspace/WorkspaceView.tsx create mode 100644 features/workspace/components/StatusCard.tsx create mode 100644 features/workspace/components/StatusGrid.tsx create mode 100644 hooks/useAuth.ts create mode 100644 hooks/useAuthSession.ts create mode 100644 hooks/useLocalStorage.ts create mode 100644 index.html create mode 100644 index.tsx create mode 100644 instructions.md create mode 100644 local_cookie.json create mode 100644 metadata.json create mode 100644 package.json create mode 100644 public/version.json create mode 100644 rules.md create mode 100644 services/apiService.ts create mode 100644 services/apiUtils.ts create mode 100644 services/appBuilder/componentService.ts create mode 100644 services/appBuilder/config.ts create mode 100644 services/appBuilder/contentService.ts create mode 100644 services/appBuilder/promptService.ts create mode 100644 services/appBuilder/rbacService.ts create mode 100644 services/appBuilderService.ts create mode 100644 services/geminiService.ts create mode 100644 services/llmService.ts create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts create mode 100644 wrangler.json diff --git a/.gitea/workflows/feature_branch_autopr.yml b/.gitea/workflows/feature_branch_autopr.yml new file mode 100644 index 0000000..14bec33 --- /dev/null +++ b/.gitea/workflows/feature_branch_autopr.yml @@ -0,0 +1,128 @@ +name: Auto-PR feature -> playtest and merge +on: + push: + branches: + - "feature-**" +jobs: + pr_merge: + runs-on: ubuntu-latest + container: + image: node:20-bullseye + defaults: + run: + shell: bash + env: + # Change if your instance URL differs + GITEA_BASE_URL: https://git.code.svchub.com + PLAYTEST_BRANCH: playtest + + # Provide this as a Gitea Actions secret in the repo (Settings -> Actions -> Secrets) + # Must have rights to create PRs, merge PRs, and delete branches. + GITEA_TOKEN: ${{ vars.GIT_TOKEN }} + + # Repo + branch from the event context (GitHub-compatible in Gitea Actions) + REPO: ${{ github.repository }} # "owner/name" + BRANCH: ${{ github.ref_name }} # e.g. "feature/issue-123" + steps: + - name: Install prerequisites (rsync, jq, git) + run: | + set -eu + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get >/dev/null 2>&1; then + apt-get update -y + apt-get install -y rsync jq git ca-certificates curl + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache rsync jq git ca-certificates curl + elif command -v dnf >/dev/null 2>&1; then + dnf install -y rsync jq git ca-certificates curl + elif command -v yum >/dev/null 2>&1; then + yum install -y rsync jq git ca-certificates curl + else + echo "Unsupported base image"; exit 1 + fi + + - name: Create PR (if needed), merge with merge-commit, delete branch + run: | + set -euo pipefail + + : "${GITEA_TOKEN:?Missing secrets.GITEA_TOKEN}" + : "${REPO:?Missing repo context}" + : "${BRANCH:?Missing branch context}" + + OWNER="${REPO%%/*}" + NAME="${REPO##*/}" + + API="${GITEA_BASE_URL%/}/api/v1" + AUTH="Authorization: token ${GITEA_TOKEN}" + + FEATURE_BRANCH="${BRANCH}" + BASE_BRANCH="${PLAYTEST_BRANCH}" + + echo "[INFO] Repo: ${OWNER}/${NAME}" + echo "[INFO] Feature branch: ${FEATURE_BRANCH}" + echo "[INFO] Base branch: ${BASE_BRANCH}" + + # 1) Create PR from feature branch to playtest + echo "[INFO] Creating PR from ${FEATURE_BRANCH} → ${BASE_BRANCH}" + + PR_PAYLOAD="$(jq -n \ + --arg title "Auto-merge ${FEATURE_BRANCH} -> ${BASE_BRANCH}" \ + --arg head "${FEATURE_BRANCH}" \ + --arg base "${BASE_BRANCH}" \ + --arg body "Automated PR created on push to ${FEATURE_BRANCH}" \ + '{title:$title, head:$head, base:$base, body:$body}')" + + CREATE_URL="${API}/repos/${OWNER}/${NAME}/pulls" + CREATE_HTTP="$(curl -sS -o /tmp/pr_create.json -w "%{http_code}" \ + -H "Content-Type: application/json" -H "$AUTH" \ + -X POST -d "$PR_PAYLOAD" \ + "$CREATE_URL" || true)" + + if [ "$CREATE_HTTP" != "201" ]; then + echo "[ERROR] PR create failed (HTTP $CREATE_HTTP):" + cat /tmp/pr_create.json || true + exit 1 + fi + + PR_NUMBER="$(jq -r '.number' /tmp/pr_create.json)" + echo "[INFO] Created PR #${PR_NUMBER}" + + # 2) Merge PR using a merge commit ("Do": "merge") + # Branch deletion is handled separately in step 4 with safety checks + MERGE_PAYLOAD="$(jq -n \ + --arg do "merge" \ + --arg title "Merge ${FEATURE_BRANCH} into ${BASE_BRANCH}" \ + --arg message "Merged automatically from ${FEATURE_BRANCH}" \ + '{Do:$do, MergeTitleField:$title, MergeMessageField:$message}')" + + MERGE_URL="${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}/merge" + MERGE_HTTP="$(curl -sS -o /tmp/pr_merge.json -w "%{http_code}" \ + -H "Content-Type: application/json" -H "$AUTH" \ + -X POST -d "$MERGE_PAYLOAD" \ + "$MERGE_URL" || true)" + + if [ "$MERGE_HTTP" != "200" ] && [ "$MERGE_HTTP" != "201" ] && [ "$MERGE_HTTP" != "204" ]; then + echo "[ERROR] PR merge failed (HTTP $MERGE_HTTP):" + cat /tmp/pr_merge.json || true + exit 1 + fi + echo "[INFO] PR #${PR_NUMBER} merged." + + # 4) Ensure branch is deleted (in case delete-after-merge is disabled) + # Safety check: only delete if branch name starts with "feature-" or "bugfix-" + if [[ "${FEATURE_BRANCH}" == feature-* ]] || [[ "${FEATURE_BRANCH}" == bugfix-* ]]; then + echo "[INFO] Deleting temporary branch: ${FEATURE_BRANCH}" + DEL_URL="${API}/repos/${OWNER}/${NAME}/branches/${FEATURE_BRANCH}" + DEL_HTTP="$(curl -sS -o /tmp/branch_del.json -w "%{http_code}" \ + -H "$AUTH" -X DELETE \ + "$DEL_URL" || true)" + + if [ "$DEL_HTTP" = "204" ] || [ "$DEL_HTTP" = "200" ]; then + echo "[INFO] Branch ${FEATURE_BRANCH} deleted." + else + echo "[WARN] Branch delete returned HTTP $DEL_HTTP (may already be deleted or not permitted)." + cat /tmp/branch_del.json || true + fi + else + echo "[WARN] Branch ${FEATURE_BRANCH} does not match feature-* pattern. Skipping deletion for safety." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..a77d919 --- /dev/null +++ b/App.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import WorkspaceContainer from './features/workspace/WorkspaceContainer'; + +/** + * App is the root-level entry point. + * It delegates to specific feature containers to maintain a clean top-level structure. + */ +const App: React.FC = () => { + return ; +}; + +export default App; \ No newline at end of file diff --git a/FIXED_cloudflare_build.yml b/FIXED_cloudflare_build.yml new file mode 100644 index 0000000..581f49f --- /dev/null +++ b/FIXED_cloudflare_build.yml @@ -0,0 +1,330 @@ +name: Cloudflare Worker AI Studio Build + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch name' + default: 'main' + clone_url: + description: 'Clone URL of repo' + required: true + original_url: + description: 'Original URL of repo' + required: false + use_original: + description: 'Use original repo URL' + default: 'false' +concurrency: + group: aistudio-build-group + cancel: false + +jobs: + deploy: + container: + image: node:20-bullseye + defaults: + run: + shell: bash + env: + BRANCH_NAME: ${{ github.event.inputs.branch || 'main' }} + CLONE_URL: ${{ github.event.inputs.clone_url }} + ORIGINAL_URL: ${{ github.event.inputs.original_url }} + USE_ORIGINAL: ${{ github.event.inputs.use_original || 'false' }} + GIT_USERNAME: ${{ vars.GIT_USERNAME }} + GIT_TOKEN: ${{ vars.GIT_TOKEN }} + CLOUDFLARE_API_TOKEN: ${{ github.event.inputs.branch == 'eteam_prod' && secrets.CF_API_TOKEN_ETEAM || secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ github.event.inputs.branch == 'eteam_prod' && vars.CF_ACCOUNT_ID_ETEAM || vars.CF_ACCOUNT_ID }} + + steps: + - name: Log input values + run: | + set -euo pipefail + echo "===== INPUT DETAILS =====" + echo "Branch: $BRANCH_NAME" + echo "Clone URL: $CLONE_URL" + echo "Original URL: ${ORIGINAL_URL:-}" + echo "Use Original: $USE_ORIGINAL" + echo "=========================" + + - name: Install prerequisites (rsync, jq, git) + run: | + set -euo pipefail + + if ! command -v git >/dev/null 2>&1; then + echo "[ERROR] git not found in image; cannot proceed without apt. Use an image that includes git." + exit 1 + fi + + if ! command -v jq >/dev/null 2>&1; then + echo "[INFO] Installing jq (static binary)..." + curl -fsSL -o /usr/local/bin/jq \ + https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 + chmod +x /usr/local/bin/jq + fi + + if ! command -v rsync >/dev/null 2>&1; then + echo "[WARN] rsync not found; continuing (workflow does not require rsync for core steps)." + fi + + jq --version + git --version + + - name: Clone repository + run: | + set -euo pipefail + TARGET_URL="$CLONE_URL" + if [ "$USE_ORIGINAL" = "true" ] && [ -n "${ORIGINAL_URL:-}" ]; then + TARGET_URL="$ORIGINAL_URL" + fi + AUTH_URL="$(echo "$TARGET_URL" | sed "s#https://#https://$GIT_USERNAME:$GIT_TOKEN@#")" + git clone --branch "$BRANCH_NAME" "$AUTH_URL" repo + cd repo + echo "[INFO] Repository cloned successfully." + git remote -v + + - name: Derive canonical repo name from origin and export + run: | + set -euo pipefail + ORIGIN_URL="$(git -C repo remote get-url origin)" + DERIVED_REPO_NAME="$(basename "${ORIGIN_URL%.git}")" + echo "[DEBUG] Derived repo name from origin: '${DERIVED_REPO_NAME}'" + echo "REPO_NAME=${DERIVED_REPO_NAME}" >> "$GITHUB_ENV" + + - name: Fetch metadata (slug + unique name) from AI Studio Manager API + run: | + set -euo pipefail + + ORIGIN_URL="$(git -C repo remote get-url origin)" + OWNER_REPO="$(echo "$ORIGIN_URL" | awk -F[/:] '{print $(NF-1)"/"$NF}' | sed 's/\.git$//')" + ENCODED_REPO_NAME="$(printf '%s' "$OWNER_REPO" | jq -s -R -r @uri)" + + if [[ "$BRANCH_NAME" == "main" ]]; then + MANAGER_API_URL="https://www.humanizeiq.ai" + elif [[ "$BRANCH_NAME" == "eteam_prod" ]]; then + MANAGER_API_URL="https://www.humanizeiq.ai" + elif [[ "$BRANCH_NAME" == "dev" ]]; then + MANAGER_API_URL="https://www.dev.humanizeiq.ai" + else + MANAGER_API_URL="https://www.playtest.humanizeiq.ai" + fi + + echo "[INFO] Using Manager API URL: $MANAGER_API_URL" + echo "[INFO] Querying AI Studio Manager API for repoName=$OWNER_REPO" + + RESPONSE="$(curl -sS -X GET \ + "${MANAGER_API_URL}/api/ai_studio_manager_api/app-builder/components/by-repo?repoName=${ENCODED_REPO_NAME}" \ + -H 'accept: application/json' \ + -H 'X-API-Key: system-key' || true)" + + echo "[DEBUG] API response: ${RESPONSE:-}" + + SLUG="$(echo "${RESPONSE:-}" | jq -r '.additional_info.slug' 2>/dev/null || echo "")" + UNIQUE_APP_CODE="$(echo "${RESPONSE:-}" | jq -r '.additional_info.unique_app_code' 2>/dev/null || echo "")" + + if [ -z "${SLUG:-}" ] || [ "${SLUG:-}" = "null" ]; then + echo "[WARN] slug missing; falling back to REPO_NAME#gais_." + BASE_NAME_VALUE="${REPO_NAME#gais_}" + else + BASE_NAME_VALUE="$SLUG" + fi + + if [ -z "${UNIQUE_APP_CODE:-}" ] || [ "${UNIQUE_APP_CODE:-}" = "null" ]; then + echo "[WARN] unique_app_code missing; falling back to REPO_NAME." + UNIQUE_NAME_VALUE="${REPO_NAME}" + else + UNIQUE_NAME_VALUE="${UNIQUE_APP_CODE}" + fi + + echo "BASE_NAME=${BASE_NAME_VALUE}" >> "$GITHUB_ENV" + echo "UNIQUE_NAME=${UNIQUE_NAME_VALUE}" >> "$GITHUB_ENV" + echo "MANAGER_API_URL=${MANAGER_API_URL}" >> "$GITHUB_ENV" + + echo "[INFO] Using BASE_NAME=${BASE_NAME_VALUE}" + echo "[INFO] Using UNIQUE_NAME=${UNIQUE_NAME_VALUE} for Worker base" + + - name: Install dependencies + working-directory: repo + run: | + set -euo pipefail + npm ci || npm install + + - name: Build AI Studio / Vite project + working-directory: repo + run: | + set -euo pipefail + BASE_NAME="${BASE_NAME:-${REPO_NAME#gais_}}" + BASE_PATH="/${BASE_NAME}/" + printf '[INFO] Using base path: %s\n' "$BASE_PATH" + npm run build --if-present -- --base "$BASE_PATH" + + - name: Copy JSON assets into dist + working-directory: repo + run: | + set -euo pipefail + cp -v ./metadata.json dist/ || echo "No JSON files found in ./config" + + - name: Install Wrangler CLI + run: | + set -euo pipefail + npm install -g wrangler + + - name: Deploy Cloudflare Worker + env: + CLOUDFLARE_API_TOKEN: ${{ env.BRANCH_NAME == 'eteam_prod' && secrets.CF_API_TOKEN_ETEAM || secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ env.BRANCH_NAME == 'eteam_prod' && vars.CF_ACCOUNT_ID_ETEAM || vars.CF_ACCOUNT_ID }} + working-directory: repo + run: | + set -euo pipefail + SAFE_BRANCH="${BRANCH_NAME//\//-}" + if [ "$SAFE_BRANCH" = "playtest" ]; then + SAFE_BRANCH="pt" + fi + + SLUG="${BASE_NAME:-${REPO_NAME#gais_}}" + WORKER_NAME="gais_${SLUG}_${SAFE_BRANCH}" + + MAX_LENGTH=54 + if [ ${#WORKER_NAME} -gt $MAX_LENGTH ]; then + echo "[WARN] Worker name '${WORKER_NAME}' is ${#WORKER_NAME} chars (max: ${MAX_LENGTH})" + HASH=$(echo -n "$WORKER_NAME" | md5sum | cut -c1-8) + MAX_SLUG_LEN=$((39 - ${#SAFE_BRANCH})) + TRUNCATED_SLUG="${SLUG:0:$MAX_SLUG_LEN}" + WORKER_NAME="gais_${TRUNCATED_SLUG}_${HASH}_${SAFE_BRANCH}" + echo "[INFO] Truncated to: ${WORKER_NAME} (${#WORKER_NAME} chars)" + fi + + echo "[INFO] Using Worker name: ${WORKER_NAME}" + + echo "[INFO] Writing wrangler.json..." + cat > wrangler.json <<'EOF' + { + "name": "PLACEHOLDER", + "compatibility_date": "1970-01-01", + "workers_dev": true + } + EOF + + jq \ + --arg name "$WORKER_NAME" \ + --arg date "$(date +%Y-%m-%d)" \ + '.name=$name | .compatibility_date=$date' \ + wrangler.json > wrangler.tmp && mv wrangler.tmp wrangler.json + + echo "[INFO] Verifying Wrangler auth..." + wrangler whoami + + echo "[INFO] Deploying..." + echo "[INFO] CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}" + wrangler deploy --assets ./dist + + - name: Update Route in Traefik Database + if: success() + run: | + set -euo pipefail + + SAFE_BRANCH="${BRANCH_NAME//\//-}" + if [ "$SAFE_BRANCH" = "playtest" ] || [ "$SAFE_BRANCH" = "pt" ]; then + ENVIRONMENT="playtest" + elif [ "$SAFE_BRANCH" = "dev" ]; then + ENVIRONMENT="nonprod" + elif [ "$SAFE_BRANCH" = "main" ]; then + ENVIRONMENT="prod" + elif [ "$SAFE_BRANCH" = "eteam_prod" ]; then + ENVIRONMENT="prod" + else + echo "[WARN] Unknown branch: $SAFE_BRANCH, defaulting to playtest" + ENVIRONMENT="playtest" + fi + + echo "[INFO] Updating route for environment: $ENVIRONMENT" + + APP_NAME="${BASE_NAME}" + + echo "[INFO] Calling route update API for app: $APP_NAME using $MANAGER_API_URL" + + ROUTE_RESPONSE="$(curl -sS -X POST \ + "${MANAGER_API_URL}/api/ai_studio_manager_api/app-builder/create-route" \ + -H 'Content-Type: application/json' \ + -H 'X-API-Key: system-key' \ + -d "{\"appName\":\"${APP_NAME}\",\"environment\":\"${ENVIRONMENT}\"}" || echo '{"status":"error","message":"API call failed"}')" + + echo "[INFO] Route update response: ${ROUTE_RESPONSE}" + + if echo "$ROUTE_RESPONSE" | jq -e '.status == "success"' > /dev/null 2>&1; then + ROUTE_URL="$(echo "$ROUTE_RESPONSE" | jq -r '.url')" + echo "[INFO] ✓ Route updated successfully: $ROUTE_URL" + else + ERROR_MSG="$(echo "$ROUTE_RESPONSE" | jq -r '.message // "Unknown error"')" + echo "[WARN] Route update may have failed: $ERROR_MSG" + echo "[WARN] Continuing workflow - route can be updated manually if needed" + fi + + - name: Create PR after successful build (NO MERGE) + if: success() + run: | + set -euo pipefail + cd repo + + ORIGIN_URL="$(git remote get-url origin)" + ORIGIN_URL="${ORIGIN_URL%.git}" + REPO_OWNER="$(echo "$ORIGIN_URL" | awk -F'/' '{print $(NF-1)}')" + REPO_NAME_ONLY="$(echo "$ORIGIN_URL" | awk -F'/' '{print $NF}')" + + GITEA_API="https://git.code.svchub.com/api/v1" + AUTH_HEADER="Authorization: token ${GIT_TOKEN}" + CURRENT_BRANCH="${BRANCH_NAME}" + + # Determine PR direction based on current branch + if [ "$CURRENT_BRANCH" = "playtest" ]; then + FROM_BRANCH="playtest" + TO_BRANCH="dev" + elif [ "$CURRENT_BRANCH" = "dev" ]; then + FROM_BRANCH="dev" + TO_BRANCH="main" + elif [ "$CURRENT_BRANCH" = "main" ]; then + if git ls-remote --exit-code --heads origin eteam_prod >/dev/null 2>&1; then + FROM_BRANCH="main" + TO_BRANCH="eteam_prod" + else + echo "[INFO] No PR rule for branch: $CURRENT_BRANCH (eteam_prod does not exist)" + exit 0 + fi + else + echo "[INFO] No PR rule for branch: $CURRENT_BRANCH" + exit 0 + fi + + echo "[INFO] Checking if PR already exists: ${FROM_BRANCH} → ${TO_BRANCH}" + + # Check if PR already exists + LIST_URL="${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME_ONLY}/pulls?state=open&base=${TO_BRANCH}&head=${REPO_OWNER}:${FROM_BRANCH}" + EXISTING_PR="$(curl -sS -H "$AUTH_HEADER" "$LIST_URL" | jq -r '.[0].number // empty')" + + if [ -n "$EXISTING_PR" ]; then + echo "[INFO] PR already exists: #${EXISTING_PR} (${FROM_BRANCH} → ${TO_BRANCH})" + echo "[INFO] Skipping PR creation to avoid duplicates" + exit 0 + fi + + echo "[INFO] Creating PR from ${FROM_BRANCH} → ${TO_BRANCH}" + PR_PAYLOAD=$(jq -n \ + --arg title "Auto PR: ${FROM_BRANCH} → ${TO_BRANCH}" \ + --arg head "$FROM_BRANCH" \ + --arg base "$TO_BRANCH" \ + --arg body "Automated PR created after successful build on ${FROM_BRANCH}" \ + '{title:$title, head:$head, base:$base, body:$body}') + + HTTP_CODE=$(curl -s -o /tmp/resp.json -w "%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -H "$AUTH_HEADER" \ + -d "$PR_PAYLOAD" \ + "${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME_ONLY}/pulls" || true) + + if [ "$HTTP_CODE" != "201" ]; then + echo "[WARN] PR creation failed or PR already exists (HTTP $HTTP_CODE). Response:" + cat /tmp/resp.json || true + else + PR_NUMBER="$(jq -r '.number' /tmp/resp.json)" + echo "[INFO] ✓ PR #${PR_NUMBER} created: ${FROM_BRANCH} → ${TO_BRANCH}" + fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e8cd9c --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +
+GHBanner +
+ +# Aura Craft Studio: Shared UI Framework + +This repository provides a reusable UI skeleton with integrated Authentication, Theme management, and AI Connection services. It is designed to work seamlessly both in Google AI Studio (Studio Mode) and in standard deployed environments. + +## 🌟 High-Level Overview + +Aura Craft Studio is a modular foundation for building AI-powered applications. It abstracts away the complexities of: +- **Authentication & RBAC:** Seamless user identity management across different hosting modes. +- **AI Connectivity:** Pre-wired access to Google Gemini models via a secure proxy. +- **Theming:** A robust light/dark mode system powered by Tailwind CSS. +- **Cloud Storage:** Standardized operations for R2 storage (upload, download, list). +- **Architecture:** A strict Container/View pattern that ensures long-term scalability and code quality. + +## 📖 Critical Documentation + +### 🚀 [instructions.md](./instructions.md) +The essential "Getting Started" guide. It details the initial setup of `metadata.json` and provides the **Mandatory Prompt Template** required for AI-accelerated feature development. + +### 📜 [rules.md](./rules.md) +The "Source of Truth" for development boundaries. It defines which parts of the system are immutable (the "Wiring") and establishes coding standards like the 200-line file limit and modular feature grouping. + +--- + +## 💻 Run Locally + +**Prerequisites:** Node.js + +1. **Install dependencies:** + `npm install` +2. **Set the API Key:** + Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key. +3. **Run the app:** + `npm run dev` + +--- + +## 🛠️ Project Links + +- **AI Studio App:** [https://ai.studio/apps/drive/1eaFbkjczgCmq_TXULG7_eSOgkyaGX1Yk](https://ai.studio/apps/drive/1eaFbkjczgCmq_TXULG7_eSOgkyaGX1Yk) +- **Organization:** HumanizeIQ diff --git a/References/api_storage.json b/References/api_storage.json new file mode 100644 index 0000000..48ad5ce --- /dev/null +++ b/References/api_storage.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Auth SecureChat API","version":"1.0.0","description":"API for authentication with SecureChat using AnythingLLM"},"servers":[{"url":"/api/r2-explorer","description":"API Base Path"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authorization (Priority 1)"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Bearer token for authorization (Priority 2)"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"auth","description":"Encrypted auth cookie for authorization (Priority 3). Provides user context for personalized operations."},"StudioCookieAuth":{"type":"apiKey","in":"header","name":"X-Studio-Cookie","description":"Encrypted auth cookie passed via X-Studio-Cookie header (Priority 3, alternative to Cookie). Provides user context for personalized operations."},"StudioCookieQueryAuth":{"type":"apiKey","in":"query","name":"X-Studio-Cookie","description":"Encrypted auth cookie passed via X-Studio-Cookie query parameter (Priority 3, alternative to Cookie/Header). Provides user context for personalized operations."}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"paths":{"/health":{"get":{"summary":"Health check endpoint","description":"Returns the status of the API","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"API is running","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"message":{"type":"string","example":"Auth SecureChat API is running"},"version":{"type":"string","example":"1.0.0"}}}}}}}}},"/auth/validate":{"get":{"summary":"Validate authentication token (GET)","description":"Validates the auth token parameter via query parameter","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"auth","in":"query","description":"Authentication token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}},"post":{"summary":"Validate authentication token (POST)","description":"Validates the auth token parameter via request body","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}}},"/auth/decrypt-and-save":{"post":{"summary":"Decrypt auth token and save user data to database","description":"Decrypts the provided auth token and saves the user data to the D1 database","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token to decrypt"}}}}}},"responses":{"200":{"description":"User data successfully saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"User data saved successfully"},"updated":{"type":"boolean","example":false},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"userData":{"type":"object","properties":{"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"email":{"type":"string","example":"humanizeiq@eteaminc.com"},"firebase_uid":{"type":"string","example":"wFyjGE1j8wclXayUvPbkF4c15f92"},"firstname":{"type":"string","example":"Rajeev"},"lastname":{"type":"string","example":"Borborah"},"company_name":{"type":"string","nullable":true,"example":null}}}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid auth parameter"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to save user data"}}}}}}}}},"/upload":{"post":{"summary":"Upload file to R2 bucket","description":"Uploads a file to R2 bucket with path structure /data//Documents///. Requires authentication (API key, Bearer token, or auth cookie). Note: An auth cookie is required to identify the user (uid) for path construction. If using API key/Bearer token, you must also provide an auth cookie (via Cookie header, X-Studio-Cookie header, or X-Studio-Cookie query parameter).","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload"},"metadata":{"type":"string","description":"JSON string containing metadata key/value pairs. Can include \"category\" field to specify file category. If category is not provided, it will be derived from file extension.","example":"{\"category\": \"Reports\", \"project\": \"Q4-2024\", \"author\": \"John Doe\"}"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File uploaded successfully"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"category":{"type":"string","example":"PDF"},"metadata":{"type":"object","example":{"category":"PDF","uploadDate":"2025-10-14T20:30:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","project":"Q4-2024","author":"John Doe"}}}}}}},"400":{"description":"Bad request - missing file or invalid metadata","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"No file provided or invalid file format"}}}}}},"401":{"description":"Unauthorized - missing or invalid auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"No auth cookie found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to upload file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/upload-generic":{"post":{"summary":"Generic system upload to R2 (no user auth cookie)","description":"Uploads a file to the R2 bucket using the same path logic as the regular /upload endpoint. Company, department, and user-specific behavior are derived from metadata instead of a user auth cookie. The request must be multipart/form-data with a file field and an optional metadata JSON string. Use the metadata fields \"company\"/\"company_name\"/\"companyName\" to control the company folder, \"department\"/\"Department\" for department, and \"user-specific\" (true/false) for user-specific vs shared paths.","tags":["Generic"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload"},"metadata":{"type":"string","description":"JSON string containing metadata key/value pairs. Should include company/department/user-specific to control path, and may include call_id, candidate_id, candidate_email, job_id, job_ref, etc.","example":"{\"uid\": \"system-generic\", \"company\": \"eTeam Inc\", \"Department\": \"Recruiting\", \"user-specific\": false, \"category\": \"AIInterviewReports\", \"call_id\": \"call_19913980616786_1763719874597\", \"candidate_id\": \"12345\", \"candidate_email\": \"candidate@example.com\", \"job_id\": \"26045639\", \"job_ref\": \"23-01265\"}"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File uploaded successfully"},"path":{"type":"string","example":"eTeam Inc/departments/Recruiting/AIInterviewReports/2025-11-21/ai-summary-19745031817153-1761908089711.md"},"category":{"type":"string","example":"AIInterviewReports"},"metadata":{"type":"object","description":"Final metadata stored with the object in R2"},"user":{"type":"object","properties":{"uid":{"type":"string","example":"system-generic"},"email":{"type":"string","example":"candidate@example.com"}}},"savedIn":{"type":"string","example":"company"}}}}}},"400":{"description":"Bad request - missing file or invalid metadata JSON","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid metadata format. Must be valid JSON."},"details":{"type":"string"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to upload file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/files":{"get":{"summary":"List user files in tree format","description":"Retrieves all files for the authenticated user, optionally filtered by category. Returns files organized in a tree structure by category and date. Requires auth cookie (via Cookie header or X-Studio-Cookie header) for user identification.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Filter files by category (e.g., \"PDF\", \"Images\", \"Documents\"). If not provided, returns all files.","example":"PDF"},{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"responses":{"200":{"description":"Files retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"category":{"type":"string","example":"PDF"},"totalFiles":{"type":"integer","example":5},"tree":{"type":"object","description":"Tree structure organized by category and date","example":{"PDF":{"2025-10-14":[{"fileId":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg==","name":"document.pdf","path":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf","size":1048576,"uploaded":"2025-10-14T20:30:00.000Z","etag":"abc123def456","httpMetadata":{"contentType":"application/pdf"},"customMetadata":{"category":"PDF","uploadDate":"2025-10-14T20:30:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","fileName":"document.pdf","fileSize":"1048576","uploadTimestamp":"1729028400000"}}],"2025-10-13":[{"fileId":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTEzL3JlcG9ydC5wZGY=","name":"report.pdf","path":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-13/report.pdf","size":2097152,"uploaded":"2025-10-13T15:20:00.000Z","etag":"xyz789abc012","httpMetadata":{"contentType":"application/pdf"},"customMetadata":{"category":"PDF","uploadDate":"2025-10-13T15:20:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","fileName":"report.pdf","fileSize":"2097152","uploadTimestamp":"1728842400000"}}]}}}}}}}},"400":{"description":"Bad request - missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Auth cookie required"},"message":{"type":"string","example":"User identification required via auth cookie"}}}}}},"401":{"description":"Unauthorized - invalid or expired auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to list files"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/files-by-department":{"get":{"summary":"List files by department","description":"Retrieves all files for a specific department, organized by category. Supports optional category filtering.","tags":["Files"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"department","in":"query","required":true,"schema":{"type":"string"},"description":"Department name to retrieve files from"},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Optional category filter (e.g., \"PDF\", \"Documents\", \"Images\")"},{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded auth cookie (alternative to header or cookie)"}],"responses":{"200":{"description":"Successfully retrieved department files","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"company":{"type":"string","example":"acme-corp"},"department":{"type":"string","example":"engineering"},"category":{"type":"string","example":"all"},"totalFiles":{"type":"integer","example":15},"tree":{"type":"object","description":"Files organized by category","additionalProperties":{"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string","description":"Base64 encoded file identifier"},"name":{"type":"string","example":"report.pdf"},"path":{"type":"string","example":"acme-corp/engineering/PDF/report.pdf"},"size":{"type":"integer","example":1024000},"uploaded":{"type":"string","format":"date-time"},"etag":{"type":"string"},"httpMetadata":{"type":"object"},"customMetadata":{"type":"object"}}}},"example":{"PDF":[{"fileId":"YWNtZS1jb3JwL2VuZ2luZWVyaW5nL1BERi9yZXBvcnQucGRm","name":"report.pdf","path":"acme-corp/engineering/PDF/report.pdf","size":1024000,"uploaded":"2024-01-15T10:30:00.000Z"}],"Documents":[{"fileId":"YWNtZS1jb3JwL2VuZ2luZWVyaW5nL0RvY3VtZW50cy9zcGVjLmRvY3g=","name":"spec.docx","path":"acme-corp/engineering/Documents/spec.docx","size":512000,"uploaded":"2024-01-16T14:20:00.000Z"}]}}}}}}},"400":{"description":"Bad request - missing department parameter or not a company user","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"department parameter is required"}}}}}},"401":{"description":"Unauthorized - invalid or missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to list department files"},"details":{"type":"string"}}}}}}}}},"/files-by-metadata":{"post":{"summary":"Get files by metadata","description":"Retrieves files based on metadata filters using the same folder structure logic as upload. Supports filtering by department, category, and user-specific flag. Requires auth cookie for user identification.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metadata":{"type":"object","properties":{"department":{"type":"string","description":"Department name (defaults to \"Everyone\" if not provided)","example":"Recruiting"},"category":{"type":"string","description":"File category (e.g., \"Resumes\", \"PDF\", \"Images\")","example":"Resumes"},"user-specific":{"type":"boolean","description":"Whether to retrieve user-specific files (defaults to true). Files without department are always user-specific.","example":true}}}},"example":{"metadata":{"department":"Recruiting","category":"Resumes","user-specific":true}}}}}},"responses":{"200":{"description":"Files retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"totalFiles":{"type":"integer","example":3},"filters":{"type":"object","properties":{"company":{"type":"string","example":"HumanizeIQ"},"department":{"type":"string","example":"Recruiting"},"category":{"type":"string","example":"Resumes"},"userSpecific":{"type":"boolean","example":true},"uid":{"type":"string","example":"7939cfe1-c6b4-4cfa-8691-ada77f011553"}}},"files":{"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string","description":"Base64 encoded file path","example":"SHVtYW5pemVJUS9kZXBhcnRtZW50cy9SZWNydWl0aW5nL1Jlc3VtZXMvNzkzOWNmZTEtYzZiNC00Y2ZhLTg2OTEtYWRhNzdmMDExNTUzLzIwMjUtMTAtMjMvcmVzdW1lLnBkZg=="},"name":{"type":"string","example":"resume.pdf"},"path":{"type":"string","example":"HumanizeIQ/departments/Recruiting/Resumes/7939cfe1-c6b4-4cfa-8691-ada77f011553/2025-10-23/resume.pdf"},"size":{"type":"integer","example":524288},"uploaded":{"type":"string","format":"date-time","example":"2025-10-23T15:30:00.000Z"},"etag":{"type":"string","example":"abc123def456"},"httpMetadata":{"type":"object","properties":{"contentType":{"type":"string","example":"application/pdf"}}},"customMetadata":{"type":"object","properties":{"category":{"type":"string","example":"Resumes"},"uploadDate":{"type":"string","example":"2025-10-23T15:30:00.000Z"},"uid":{"type":"string","example":"7939cfe1-c6b4-4cfa-8691-ada77f011553"},"fileName":{"type":"string","example":"resume.pdf"},"department":{"type":"string","example":"Recruiting"}}}}}}}}}}},"400":{"description":"Bad request - missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Auth cookie required"},"message":{"type":"string","example":"User identification required via auth cookie"}}}}}},"401":{"description":"Unauthorized - invalid or expired auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to get files by metadata"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/download-file":{"get":{"summary":"Get file contents by fileId or path","description":"Retrieves a file from R2 storage by its encoded fileId (recommended) or full path. Returns the file contents with appropriate headers including metadata. The fileId is provided in the /files endpoint response.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"fileId","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded file ID (obtained from /files endpoint)","example":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg=="},{"name":"path","in":"query","required":false,"schema":{"type":"string"},"description":"Full path to the file in R2 storage (alternative to fileId)","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}],"responses":{"200":{"description":"File retrieved successfully","headers":{"Content-Type":{"schema":{"type":"string"},"description":"MIME type of the file","example":"application/pdf"},"Content-Length":{"schema":{"type":"string"},"description":"Size of the file in bytes","example":"1048576"},"ETag":{"schema":{"type":"string"},"description":"Entity tag for the file","example":"abc123def456"},"Last-Modified":{"schema":{"type":"string"},"description":"ISO 8601 timestamp of when the file was uploaded","example":"2025-10-14T20:30:00.000Z"},"X-File-Path":{"schema":{"type":"string"},"description":"Full path of the file in R2","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"X-Custom-Metadata":{"schema":{"type":"string"},"description":"JSON string of custom metadata associated with the file","example":"{\"category\":\"PDF\",\"uploadDate\":\"2025-10-14T20:30:00.000Z\",\"uid\":\"426bcea5-adb9-4580-a8ca-3a40fdb0ef85\"}"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Bad request - missing fileId/path parameter or invalid fileId","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Either fileId or path parameter is required"}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to get file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/update-file":{"put":{"summary":"Update an existing file","description":"Updates an existing file in R2 storage by replacing its content. The file is identified by fileId (recommended) or path. Optionally updates metadata while preserving existing metadata.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"fileId","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded file ID (obtained from /files endpoint)","example":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg=="},{"name":"path","in":"query","required":false,"schema":{"type":"string"},"description":"Full path to the file in R2 storage (alternative to fileId)","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The new file content to replace the existing file"},"metadata":{"type":"string","description":"Optional JSON string of metadata to update (merged with existing metadata)","example":"{\"version\":\"2.0\",\"updatedBy\":\"user@example.com\"}"}}}}}},"responses":{"200":{"description":"File updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"File updated successfully"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"size":{"type":"integer","example":2097152},"contentType":{"type":"string","example":"application/pdf"},"metadata":{"type":"object","example":{"category":"PDF","version":"2.0","updatedBy":"user@example.com","lastModified":"2025-10-15T20:30:00.000Z"}}}}}}},"400":{"description":"Bad request - missing parameters or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File is required"}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to update file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/delete-file":{"delete":{"tags":["Files"],"summary":"Delete a file","description":"Delete a file from R2 storage by fileId or path. Requires authentication.","parameters":[{"name":"fileId","in":"query","schema":{"type":"string"},"description":"The encoded file ID (base64 of the R2 key)"},{"name":"path","in":"query","schema":{"type":"string"},"description":"The direct path to the file in R2 (alternative to fileId)"}],"responses":{"200":{"description":"File deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File deleted successfully"},"path":{"type":"string","example":"consumer/data/uid/Documents/test.txt"}}}}}},"400":{"description":"Bad Request - Missing or invalid parameters","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"fileId parameter is required"}}}}}},"401":{"description":"Unauthorized - Invalid or missing auth cookie"},"404":{"description":"Not Found - File does not exist","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"},"path":{"type":"string"}}}}}},"500":{"description":"Internal Server Error"}}}}}} \ No newline at end of file diff --git a/References/manager_api.json b/References/manager_api.json new file mode 100644 index 0000000..4bb54ff --- /dev/null +++ b/References/manager_api.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"AI Studio Manager API","version":"1.0.0","description":"API for AI Studio Manager including authentication and app builder functionality"},"servers":[{"url":"/api/ai_studio_manager_api","description":"Auth API Base Path"},{"url":"/api","description":"App Builder API Base Path"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authorization"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Bearer token for authorization"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"auth","description":"Authentication cookie (encrypted)"},"StudioCookieAuth":{"type":"apiKey","in":"query","name":"X-Studio-Cookie","description":"Base64-encoded encrypted cookie for Google AI Studio"}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"tags":[{"name":"Authentication","description":"Authentication and user management endpoints"},{"name":"App Builder","description":"App builder endpoints for managing applications and repositories"},{"name":"CMS","description":"Content Management System endpoints for managing companies, projects, and components"},{"name":"Work Management","description":"Work management endpoints for managing tasks"},{"name":"RBAC","description":"Role-Based Access Control endpoints for managing roles, permissions, and user assignments"}],"paths":{"/health":{"get":{"summary":"Health check endpoint","description":"Returns the status of the API","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"API is running","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"message":{"type":"string","example":"Auth SecureChat API is running"},"version":{"type":"string","example":"1.0.0"}}}}}}}}},"/auth/validate":{"get":{"summary":"Validate authentication token (GET)","description":"Validates the auth token parameter via query parameter","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"auth","in":"query","description":"Authentication token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}},"post":{"summary":"Validate authentication token (POST)","description":"Validates the auth token parameter via request body","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}}},"/auth/decrypt-and-save":{"post":{"summary":"Decrypt auth token and save user data to database","description":"Decrypts the provided auth token and saves the user data to the D1 database","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token to decrypt"}}}}}},"responses":{"200":{"description":"User data successfully saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"User data saved successfully"},"updated":{"type":"boolean","example":false},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"userData":{"type":"object","properties":{"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"email":{"type":"string","example":"humanizeiq@eteaminc.com"},"firebase_uid":{"type":"string","example":"wFyjGE1j8wclXayUvPbkF4c15f92"},"firstname":{"type":"string","example":"Rajeev"},"lastname":{"type":"string","example":"Borborah"},"company_name":{"type":"string","nullable":true,"example":null}}}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid auth parameter"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to save user data"}}}}}}}}},"/app-builder/apps":{"get":{"summary":"Get all apps","description":"Retrieves all apps from the database (no user filter)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"responses":{"200":{"description":"List of apps","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"my-app"},"createdBy":{"type":"string","example":"user@example.com"},"createdAt":{"type":"string","format":"date-time","example":"2025-01-01T12:00:00Z"},"userId":{"type":"string","example":"user123"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Unauthorized"}}}}}}}},"post":{"summary":"Create a new app","description":"Creates a new app entry in the database. App names must be unique across all users.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"201":{"description":"App created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"App 'my-app' created successfully."},"appId":{"type":"integer","example":1}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Authentication required. Please provide valid credentials."}}}}}},"409":{"description":"App already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"An app with this name already exists."}}}}}}}}},"/app-builder/add-github-webhook":{"post":{"summary":"Add or update webhook in GitHub repository","description":"Adds a webhook to the specified GitHub repository. If the webhook already exists, it updates the webhook to match current specifications. The webhook URL is https://services.nonprod.svchub.com/git_webhooks/github_cloner with content type application/json, SSL verification enabled, webhook secret from WEBHOOK_SECRET environment variable, and sends all events.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"}}}}}},"responses":{"200":{"description":"Webhook updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook updated successfully"},"alreadyExists":{"type":"boolean","example":true},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123456},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/github_cloner"},"events":{"type":"array","items":{"type":"string"},"example":["*"]},"active":{"type":"boolean","example":true}}}}}}}},"201":{"description":"Webhook created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook created successfully"},"alreadyExists":{"type":"boolean","example":false},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123456},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/github_cloner"},"events":{"type":"array","items":{"type":"string"},"example":["*"]},"active":{"type":"boolean","example":true}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"401":{"description":"Authentication or credentials error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub credentials are invalid. Please update your credentials."}}}}}},"404":{"description":"Repository or credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/github-credentials":{"get":{"summary":"Get GitHub credentials","description":"Retrieves the saved GitHub username (token is never returned)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"responses":{"200":{"description":"GitHub username","content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","example":"jane-doe"}}}}}},"404":{"description":"Credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Credentials not found for this user."}}}}}}}},"post":{"summary":"Save GitHub credentials","description":"Saves or updates GitHub username and personal access token","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","token"],"properties":{"username":{"type":"string","example":"jane-doe"},"token":{"type":"string","example":"ghp_xxxxxxxxxxxx"}}}}}},"responses":{"200":{"description":"Credentials saved","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Credentials saved successfully."}}}}}},"400":{"description":"Invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Invalid username or token provided."}}}}}}}}},"/app-builder/check-github-repo":{"post":{"summary":"Check GitHub repository existence","description":"Checks if a repository exists using stored GitHub credentials","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"200":{"description":"Repository check result","content":{"application/json":{"schema":{"type":"object","properties":{"exists":{"type":"boolean","example":true}}}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub credentials are invalid. Please update your credentials."}}}}}},"404":{"description":"No credentials found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No GitHub credentials found. Please save your credentials first."}}}}}}}}},"/app-builder/check-gitea-repo":{"get":{"summary":"Check Gitea repository existence","description":"Checks if a repository exists in HumanizeIQ Gitea","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"appName","in":"query","required":true,"schema":{"type":"string"},"example":"my-cool-app"}],"responses":{"200":{"description":"Repository check result","content":{"application/json":{"schema":{"type":"object","properties":{"exists":{"type":"boolean","example":false}}}}}}}}},"/app-builder/add-gitea-webhook":{"post":{"summary":"Add or update webhook in Gitea repository","description":"Adds a webhook to the specified Gitea repository. If the webhook already exists, it updates the webhook to match current specifications. The webhook URL is https://services.nonprod.svchub.com/git_webhooks/gitea with content type application/json, triggers on push and pull request closed events for all branches, and includes Bearer system-key authorization header.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"}}}}}},"responses":{"200":{"description":"Webhook updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook updated successfully"},"alreadyExists":{"type":"boolean","example":true},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/gitea"},"events":{"type":"array","items":{"type":"string"},"example":["push","pull_request_closed"]},"branch_filter":{"type":"string","example":"*"},"active":{"type":"boolean","example":true}}}}}}}},"201":{"description":"Webhook created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook created successfully"},"alreadyExists":{"type":"boolean","example":false},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/gitea"},"events":{"type":"array","items":{"type":"string"},"example":["push","pull_request_closed"]},"branch_filter":{"type":"string","example":"*"},"active":{"type":"boolean","example":true}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"404":{"description":"Repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Gitea repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/gitea-pull-requests":{"get":{"tags":["App Builder"],"summary":"Get open pull requests from Gitea","description":"Retrieves all open pull requests for a component's Gitea repository","security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"componentId","in":"query","required":true,"description":"Component ID to fetch pull requests for","schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Pull requests retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"pullRequests":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":12345},"number":{"type":"integer","example":42},"title":{"type":"string","example":"Merge playtest into dev"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/repo/pulls/42"},"sourceBranch":{"type":"string","example":"playtest"},"targetBranch":{"type":"string","example":"dev"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"author":{"type":"string","example":"username"},"mergeable":{"type":"boolean","example":true},"merged":{"type":"boolean","example":false},"draft":{"type":"boolean","example":false},"envName":{"type":"string","example":"NonProd","nullable":true},"environment":{"type":"string","example":"dev","nullable":true}}}},"count":{"type":"integer","example":1}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required."}}}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Authentication required. Please provide valid credentials."}}}}}},"404":{"description":"Component or repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"An unexpected error occurred while fetching pull requests. Please try again."}}}}}}}}},"/app-builder/gitea-pr-status":{"get":{"summary":"Get Gitea pull request status","description":"Gets the status of a specific pull request by ID, including merge status, state, conflict status, and branch information.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"appName","in":"query","required":true,"schema":{"type":"string"},"example":"my-app"},{"name":"prId","in":"query","required":true,"schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Pull request status retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"pr":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Feature: Add new functionality"},"state":{"type":"string","example":"open"},"merged":{"type":"boolean","example":false},"mergeable":{"type":"boolean","example":true},"hasConflicts":{"type":"boolean","example":false,"description":"True when mergeable is false, indicating merge conflicts"},"base":{"type":"string","example":"main"},"head":{"type":"string","example":"dev"},"created_at":{"type":"string","example":"2025-01-01T12:00:00Z"},"updated_at":{"type":"string","example":"2025-01-02T12:00:00Z"},"merged_at":{"type":"string","nullable":true,"example":null},"html_url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app/pulls/1"}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName query parameter is required."}}}}}},"404":{"description":"Pull request not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request not found. Please check the PR ID and app name."}}}}}}}}},"/app-builder/merge-gitea-pr":{"post":{"summary":"Merge and close Gitea pull request","description":"Initiates an asynchronous merge of a pull request in Gitea using merge commit strategy with force merge enabled. Returns immediately with status 202. Automatically creates route entries: playtest→dev creates dev route in ROUTES_DB (services.nonprod.svchub.com/{app_name}), dev→main creates prod route in ROUTES_DB_PROD (services.prod.svchub.com/{app_name}). Use the PR status endpoint to check merge completion.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","prId"],"properties":{"appName":{"type":"string","example":"my-app"},"prId":{"type":"integer","example":1,"description":"Pull request ID/number"}}}}}},"responses":{"202":{"description":"Pull request merge initiated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Pull request #1 merge initiated. Use the PR status endpoint to check merge status."},"prId":{"type":"integer","example":1},"initiated":{"type":"boolean","example":true}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"404":{"description":"Pull request not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request not found. Please check the PR ID and app name."}}}}}},"405":{"description":"Pull request cannot be merged","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Cannot merge PR: PR is not mergeable"}}}}}},"409":{"description":"Pull request already merged","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request has already been merged."}}}}}}}}},"/app-builder/redeploy-app":{"post":{"summary":"Redeploy an app","description":"Triggers a redeploy of an app on a specific branch by calling {GITEA_WEBHOOK_BASE_URL}/redeploy with Authorization Bearer token and X-GitHub-Event header (base URL configurable via GITEA_WEBHOOK_BASE_URL, auth token via GITEA_WEBHOOK_AUTH_TOKEN)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","branch"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app to redeploy"},"branch":{"type":"string","enum":["main","dev","playtest"],"example":"main","description":"Branch to deploy (main, dev, or playtest)"}}}}}},"responses":{"200":{"description":"Redeploy triggered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Redeploy triggered successfully for gais_my_app on branch main"},"repositoryName":{"type":"string","example":"gais_my_app"},"branch":{"type":"string","example":"main"},"cloneUrl":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"502":{"description":"Redeploy service error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Redeploy service error: 500 - Internal Server Error"}}}}}}}}},"/app-builder/force-deploy":{"post":{"summary":"Force deploy an app","description":"Triggers a force deploy workflow for an app via webhook service. This bypasses normal deployment checks and forces a deployment for a specific PR.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","prId"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"},"prId":{"type":"integer","example":1,"description":"Pull request ID/number"}}}}}},"responses":{"200":{"description":"Force deploy workflow triggered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Force deploy workflow triggered successfully for gais_my_app"},"appName":{"type":"string","example":"my-app"},"prId":{"type":"integer","example":1},"repository":{"type":"string","example":"HumanizeIQ/gais_my_app"},"httpStatus":{"type":"integer","example":204},"giteaResponse":{"type":"string","nullable":true}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"403":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Unauthorized: Invalid webhook authentication token"}}}}}},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Force deploy webhook endpoint not found"}}}}}}}}},"/app-builder/create-defect-issue":{"post":{"summary":"Create a defect issue","description":"Creates a defect issue in the Gitea repository for an app with optional file attachments. Attachments are uploaded first, then linked in the issue body.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","title"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"},"title":{"type":"string","example":"Bug: Login button not working","description":"Issue title"},"description":{"type":"string","example":"When clicking the login button, nothing happens. Expected behavior: should redirect to dashboard.","description":"Issue description/body (optional)"},"attachments":{"type":"array","description":"Optional array of file attachments","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"screenshot.png","description":"Filename with extension"},"type":{"type":"string","example":"image/png","description":"MIME type (optional, defaults to application/octet-stream)"},"data":{"type":"string","example":"iVBORw0KGgoAAAANSUhEUgAAAAUA...","description":"Base64 encoded file content (with or without data URI prefix)"}}}}}}}}},"responses":{"201":{"description":"Defect issue created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Defect issue #5 created successfully"},"issue":{"type":"object","properties":{"id":{"type":"integer","example":123},"number":{"type":"integer","example":5},"title":{"type":"string","example":"Bug: Login button not working"},"body":{"type":"string","example":"Description...\n\n### Attachments\n- [screenshot.png](https://...)"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app/issues/5"},"created_at":{"type":"string","example":"2025-01-01T12:00:00Z"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"screenshot.png"},"url":{"type":"string","example":"https://git.code.svchub.com/..."}}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required."}}}}}},"403":{"description":"Access forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Access forbidden. Please check token permissions."}}}}}},"404":{"description":"Repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/create-gitea-repo":{"post":{"summary":"Create Gitea repository","description":"Creates a new repository in HumanizeIQ Gitea instance with three branches: main, dev, and playtest. Also saves the app to the database. App names must be unique - returns 409 if an app with the same name already exists.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"201":{"description":"Repository created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Repository 'gais_my_app' created successfully with branches: main, dev, playtest."},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app"},"branches":{"type":"array","items":{"type":"object","properties":{"branch":{"type":"string","example":"dev"},"created":{"type":"boolean","example":true},"alreadyExists":{"type":"boolean","example":false}}}}}}}}},"409":{"description":"App name already exists or repository already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"A repository with this name already exists. Missing branches have been created."},"branches":{"type":"array","items":{"type":"object","properties":{"branch":{"type":"string","example":"dev"},"created":{"type":"boolean","example":false},"alreadyExists":{"type":"boolean","example":true}}}}}}}}}}}},"/app-builder/create-route":{"post":{"summary":"Create or update route entry","description":"Creates a new route entry in the routes database for playtest, nonprod, or prod environments. If a route already exists for the app, it updates the existing route. All URLs are configurable via BASE_URL and WORKER_DOMAIN environment variables.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app"},"environment":{"type":"string","enum":["playtest","nonprod","prod"],"default":"playtest","example":"playtest","description":"Environment to create route for (playtest, nonprod, or prod)"}}}}}},"responses":{"200":{"description":"Route updated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Route for 'my-app' in nonprod environment updated successfully."},"url":{"type":"string","example":"https://services.nonprod.svchub.com/my_app"},"environment":{"type":"string","example":"nonprod"},"updated":{"type":"boolean","example":true}}}}}},"201":{"description":"Route created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Route for 'my-app' in playtest environment created successfully."},"url":{"type":"string","example":"https://services.nonprod.svchub.com/playtest/my_app"},"environment":{"type":"string","example":"playtest"},"updated":{"type":"boolean","example":false}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Invalid environment. Must be one of: playtest, nonprod, prod"}}}}}}}}},"/app-builder/organizations":{"get":{"tags":["App Builder"],"summary":"Get all organizations","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"List of organizations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Organization created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"organization":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"409":{"description":"Conflict - organization already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update an existing organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Updated Org"},"description":{"type":"string","example":"Updated Description"}}}}}},"responses":{"200":{"description":"Organization updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Organization updated successfully."},"organization":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Updated Org"},"description":{"type":"string","example":"Updated Description"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization ID and name are required."}}}}}},"404":{"description":"Organization not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization not found"}}}}}},"409":{"description":"Conflict - organization name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization with this name already exists"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update organization"}}}}}}}}},"/app-builder/projects":{"get":{"tags":["App Builder"],"summary":"Get all projects","description":"Retrieve all projects, optionally filtered by organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"organizationId","in":"query","description":"Filter projects by organization ID","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - invalid organizationId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","organizationId"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"}}}}}},"responses":{"201":{"description":"Project created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"project":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"404":{"description":"Organization not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"409":{"description":"Conflict - project already exists in organization","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update a project","description":"Updates an existing project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":103,"description":"Project ID"},"name":{"type":"string","example":"Updated Project Name","description":"Project name (optional)"},"description":{"type":"string","example":"Updated project description","description":"Project description (optional)"}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Project updated successfully."},"project":{"type":"object","properties":{"id":{"type":"integer","example":103},"name":{"type":"string","example":"Updated Project"},"description":{"type":"string","example":"Updated Description"},"organization_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project ID is required."}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update project"}}}}}}}}},"/cms/companies":{"get":{"tags":["CMS"],"summary":"Get all companies","description":"Retrieve all companies in the CMS","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"List of companies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"eTeam"},"slug":{"type":"string","example":"eteam"},"description":{"type":"string","example":"eTeam Inc."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch companies"}}}}}}}}},"/cms/companies/{companySlug}":{"get":{"tags":["CMS"],"summary":"Get company by slug","description":"Retrieve a specific company by its slug","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}}],"responses":{"200":{"description":"Company details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"eTeam"},"slug":{"type":"string","example":"eteam"},"description":{"type":"string","example":"eTeam Inc."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Company not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch company"}}}}}}}}},"/cms/companies/{companySlug}/projects":{"get":{"tags":["CMS"],"summary":"Get projects for a company","description":"Retrieve all projects for a specific company","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Corporate Website"},"slug":{"type":"string","example":"corporate-web"},"description":{"type":"string","example":"Main corporate website"},"company_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Company not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch projects"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}":{"get":{"tags":["CMS"],"summary":"Get project by slug","description":"Retrieve a specific project by its slug","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}}],"responses":{"200":{"description":"Project details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Corporate Website"},"slug":{"type":"string","example":"corporate-web"},"description":{"type":"string","example":"Main corporate website"},"company_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch project"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}/components":{"get":{"tags":["CMS"],"summary":"Get components for a project","description":"Retrieve all components for a specific project (metadata only, no fields)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}}],"responses":{"200":{"description":"List of components","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Home Page"},"slug":{"type":"string","example":"homePage"},"project_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch components"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}/components/{componentSlug}":{"get":{"tags":["CMS"],"summary":"Get component content","description":"Retrieve a specific component with its fields and content","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}},{"name":"componentSlug","in":"path","required":true,"description":"Component slug identifier","schema":{"type":"string","example":"homePage"}}],"responses":{"200":{"description":"Component with fields","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Home Page"},"slug":{"type":"string","example":"homePage"},"project_id":{"type":"integer","example":1},"fields":{"type":"array","description":"Array of field definitions with values","items":{"type":"object","properties":{"key":{"type":"string","example":"hero"},"label":{"type":"string","example":"Hero Section"},"type":{"type":"string","enum":["text","textarea","rich-text","image","video","link","number","boolean","date","icon-key","group","list","simple-list"],"example":"group"},"value":{"description":"Field value (type depends on field type)","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array"},{"type":"object"},{"type":"null"}]},"subFields":{"type":"array","description":"Nested fields for group type","items":{"type":"object"}},"items":{"type":"array","description":"Array items for list type","items":{"type":"object"}}}},"example":[{"key":"hero","label":"Hero Section","type":"group","subFields":[{"key":"title","label":"Title","type":"text","value":"Welcome to eTeam"},{"key":"image","label":"Background Image","type":"image","value":"https://example.com/hero.jpg"}]}]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components/by-repo":{"get":{"tags":["App Builder"],"summary":"Get component by GitHub repo name","description":"Retrieve component definition based on the GitHub repository name (hiq_repo)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"repoName","in":"query","description":"GitHub repository name (hiq_repo)","required":true,"schema":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"}}],"responses":{"200":{"description":"Component found","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Handles user auth and profile"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["api.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"}}}}}}}},"400":{"description":"Bad request - missing repoName","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"repoName query parameter is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found with the specified repo name."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components/by-github-repo":{"get":{"tags":["App Builder"],"summary":"Get component by GitHub repo name","description":"Retrieve component definition based on the GitHub repository name (github_repo field)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"githubRepo","in":"query","description":"GitHub repository name (github_repo)","required":true,"schema":{"type":"string","example":"web-dash"}}],"responses":{"200":{"description":"Component found","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"UX"},"description":{"type":"string","example":"User interface dashboard"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."}}}}}}}},"400":{"description":"Bad request - missing githubRepo","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"githubRepo query parameter is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found with the specified GitHub repo name."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components":{"get":{"tags":["App Builder"],"summary":"Get components for a project","description":"Retrieve all components for a specific project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"projectId","in":"query","description":"Project ID to filter components","required":true,"schema":{"type":"integer","example":101}}],"responses":{"200":{"description":"List of components","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Handles user auth and profile"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code: organizationname_projectname_slug"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"user-service-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["api.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"}}}}}}}}},"400":{"description":"Bad request - missing or invalid projectId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"projectId query parameter is required."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch components"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new component","description":"Create a new component within a specific project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["projectId","name","title","type","additional_info"],"properties":{"projectId":{"type":"integer","example":101},"name":{"type":"string","example":"reporting-service"},"title":{"type":"string","example":"Reporting Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Generates reports"},"additional_info":{"type":"object","required":["slug","supported_domains","hiq_repo"],"properties":{"slug":{"type":"string","example":"reporting-service"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*"]},"hiq_repo":{"type":"string","example":"reporting-repo"},"ai_studio_link":{"type":"string","description":"UX components only","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","description":"UX components only","example":"web-dash"},"github_owner":{"type":"string","description":"UX components only","example":"org-name"}}}}}}}},"responses":{"201":{"description":"Component created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Component created successfully."},"component":{"type":"object","properties":{"id":{"type":"integer","example":203},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"reporting-service"},"title":{"type":"string","example":"Reporting Service"},"type":{"type":"string","example":"API"},"description":{"type":"string","example":"Generates reports"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Pending"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_reportingservice","description":"Auto-generated, immutable"},"slug":{"type":"string","example":"reporting-service"},"hiq_repo":{"type":"string","example":"reporting-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*"]}}}}}}}}}},"400":{"description":"Bad request - missing required fields or invalid type","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project ID, name, title, and type are required."}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"409":{"description":"Conflict - component with same name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component with this name already exists in this project"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create component"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update an existing component","description":"Update an existing component within a specific project. All fields except id are optional.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":203},"name":{"type":"string","example":"updated-service"},"title":{"type":"string","example":"Updated Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Updated description"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active","description":"Component status"},"additional_info":{"type":"object","properties":{"slug":{"type":"string","example":"updated-service"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]},"hiq_repo":{"type":"string","example":"updated-repo"},"ai_studio_link":{"type":"string","description":"UX components only","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","description":"UX components only","example":"web-dash"},"github_owner":{"type":"string","description":"UX components only","example":"org-name"}}}}}}}},"responses":{"200":{"description":"Component updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Component updated successfully."},"component":{"type":"object","properties":{"id":{"type":"integer","example":203},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"updated-service"},"title":{"type":"string","example":"Updated Service"},"type":{"type":"string","example":"API"},"description":{"type":"string","example":"Updated description"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_updatedservice","description":"Auto-generated, immutable (cannot be modified)"},"slug":{"type":"string","example":"updated-service"},"hiq_repo":{"type":"string","example":"updated-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]}}}}}}}}}},"400":{"description":"Bad request - missing component ID or invalid type","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component ID is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"409":{"description":"Conflict - component name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component with this name already exists in this project"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update component"}}}}}}}}},"/app-builder/components/{componentId}/invite-collaborator":{"post":{"tags":["App Builder"],"summary":"Invite user as GitHub collaborator","description":"Invite the authenticated user as a collaborator on the component's GitHub repository using the repository owner's credentials","security":[{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"path","description":"Component ID","required":true,"schema":{"type":"integer","example":36}}],"responses":{"200":{"description":"Collaborator invited successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Invitation sent to username"},"invited":{"type":"boolean","example":true},"alreadyCollaborator":{"type":"boolean","example":false},"repository":{"type":"string","example":"hiq-automation/craft-taskmanager"},"collaborator":{"type":"string","example":"github-username"}}}}}},"400":{"description":"Bad request - missing configuration or credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component does not have a GitHub repository configured"}}}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication required"}}}}}},"404":{"description":"Component or credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to invite collaborator"},"details":{"type":"string","example":"GitHub API error: 403 - ..."}}}}}}}}},"/app-builder/requirements":{"get":{"tags":["App Builder"],"summary":"Get requirements for a component","description":"Retrieve all requirements for a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","description":"Component ID","required":true,"schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Requirements retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required and must be a valid number."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch requirements"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Save requirements","description":"Create new or update existing requirements for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","requirements"],"properties":{"componentId":{"type":"integer","example":201,"description":"Component ID"},"requirements":{"type":"array","description":"Array of requirements to save","items":{"type":"object","required":["type","title","status"],"properties":{"id":{"type":"integer","example":301,"description":"Optional - include for updates, omit for new requirements"},"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Open"}}}}}}}}},"responses":{"200":{"description":"Requirements saved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Requirements saved successfully."},"requirements":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user..."},"status":{"type":"string","example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Each requirement must have type, title, and status."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to save requirements"}}}}}}}}},"/app-builder/requirements/by-hash":{"get":{"tags":["App Builder"],"summary":"Get requirement by unique hash","description":"Retrieve a specific requirement using its unique hash identifier (SHA-256 hash of componentId-appCode-requirementId)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"hash","in":"query","description":"Unique hash identifier for the requirement","required":true,"schema":{"type":"string","example":"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"}}],"responses":{"200":{"description":"Requirement retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"requirement":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing hash parameter","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"hash query parameter is required."}}}}}},"404":{"description":"Requirement not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Requirement not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch requirement"}}}}}}}}},"/app-builder/requirements/backfill-hashes":{"post":{"tags":["App Builder"],"summary":"Backfill unique hashes for requirements","description":"Generates and updates unique_hash for all requirements that don't have it populated. This is useful for migrating existing data after adding the unique_hash feature.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Backfill completed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"processed":{"type":"integer","example":25,"description":"Total requirements processed"},"updated":{"type":"integer","example":23,"description":"Requirements successfully updated with hash"},"skipped":{"type":"integer","example":2,"description":"Requirements skipped due to errors"},"message":{"type":"string","example":"Backfill complete: 23 updated, 2 skipped"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to backfill requirement hashes"},"details":{"type":"string","example":"Database connection error"}}}}}}}}},"/app-builder/requirements/{id}":{"patch":{"tags":["App Builder"],"summary":"Update a requirement","description":"Update individual fields of a requirement. Only the fields provided in the request body will be updated.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","description":"Requirement ID","required":true,"schema":{"type":"integer","example":301}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature","description":"Optional - Requirement type"},"title":{"type":"string","example":"Updated User Login","description":"Optional - Requirement title"},"description":{"type":"string","example":"Updated description for user login","description":"Optional - Requirement description"},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Resolved","description":"Optional - Requirement status"}},"description":"At least one field must be provided"},"examples":{"Update status only":{"value":{"status":"Resolved"}},"Update multiple fields":{"value":{"status":"InProgress","description":"Working on implementing OAuth2 authentication"}}}}}},"responses":{"200":{"description":"Requirement updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Requirement updated successfully."},"requirement":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","example":"Resolved"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No valid fields provided for update. Allowed fields: type, title, description, status"}}}}}},"404":{"description":"Requirement not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Requirement not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update requirement"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/app-builder/get_requirements_from_code":{"get":{"tags":["App Builder"],"summary":"Get file from code repository","description":"Fetches a file from a specified folder in the Gitea repository (playtest branch)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"hiq_repo","in":"query","description":"Gitea repository name in owner/repo format (e.g., HumanizeIQ/gais_myapp)","required":true,"schema":{"type":"string","example":"HumanizeIQ/gais_myapp"}},{"name":"folder","in":"query","description":"Folder path in the repository (optional, defaults to root)","required":false,"schema":{"type":"string","example":"docs"}},{"name":"file_name","in":"query","description":"Name of the file to fetch (optional, defaults to requirements.md)","required":false,"schema":{"type":"string","example":"requirements.md"}}],"responses":{"200":{"description":"File retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"content":{"type":"string","example":"# Requirements\n\n## Feature 1\n..."},"sha":{"type":"string","example":"abc123..."},"size":{"type":"integer","example":1024},"path":{"type":"string","example":"docs/requirements.md"},"name":{"type":"string","example":"requirements.md"}}}}}},"400":{"description":"Bad request - missing or invalid hiq_repo","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"hiq_repo query parameter is required."}}}}}},"404":{"description":"File not found in repository","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"File 'docs/requirements.md' not found in repository."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch file from repository."}}}}}}}}},"/app-builder/create-pull-request":{"post":{"tags":["App Builder"],"summary":"Create a pull request in Gitea","description":"Creates a pull request from playtest to dev or dev to main in the component's Gitea repository","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","targetBranch"],"properties":{"componentId":{"type":"integer","description":"Component ID","example":201},"targetBranch":{"type":"string","enum":["dev","main"],"description":"Target branch for the pull request (dev or main). Source branch is automatically determined: playtest→dev or dev→main","example":"dev"},"comment":{"type":"string","description":"Optional comment/description for the pull request","example":"Merging latest features from playtest"}}}}}},"responses":{"201":{"description":"Pull request created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Pull request created successfully."},"pullRequest":{"type":"object","properties":{"id":{"type":"integer","example":42},"number":{"type":"integer","example":5},"title":{"type":"string","example":"Merge playtest into dev"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_myapp/pulls/5"},"sourceBranch":{"type":"string","example":"playtest"},"targetBranch":{"type":"string","example":"dev"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid parameters","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"targetBranch must be either \"dev\" or \"main\"."}}}}}},"404":{"description":"Component, repository, or branch not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"409":{"description":"Pull request already exists or branches are identical","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request already exists or branches are identical."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create pull request."}}}}}}}}},"/app-builder/prompts":{"get":{"tags":["App Builder"],"summary":"Get system AI prompts","description":"Retrieves the AI system prompts configuration from the server","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"System prompts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"documentation_assistant":{"type":"string"},"ideation_product_manager":{"type":"string"},"design_architect":{"type":"string"},"requirements_product_owner":{"type":"string"},"builder_prompt_engineer":{"type":"string"},"requirements_generator_modal":{"type":"string"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch system prompts"}}}}}}}}},"/app-builder/component-prompts":{"get":{"tags":["App Builder"],"summary":"Get component prompts","description":"Retrieves all prompts associated with a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","required":true,"description":"Component ID","schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Component prompts retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":401},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"System Prompt"},"description":{"type":"string","example":"Core system instruction"},"type":{"type":"string","enum":["System","Other"],"example":"System"},"content":{"type":"string","example":"You are an AI..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component prompts"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create component prompt","description":"Creates a new prompt for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","title","type","content"],"properties":{"componentId":{"type":"integer","example":201},"title":{"type":"string","example":"New Prompt"},"description":{"type":"string","example":"Prompt description"},"type":{"type":"string","enum":["System","Other"],"example":"Other"},"content":{"type":"string","example":"You are an AI assistant..."}}}}}},"responses":{"201":{"description":"Prompt created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt created successfully."},"prompt":{"type":"object","properties":{"id":{"type":"integer","example":402},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"New Prompt"},"description":{"type":"string","example":"Desc"},"type":{"type":"string","example":"Other"},"content":{"type":"string","example":"Content"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create prompt"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update component prompt","description":"Updates an existing component prompt","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":402},"title":{"type":"string","example":"Updated Title"},"description":{"type":"string","example":"Updated description"},"type":{"type":"string","enum":["System","Other"],"example":"Other"},"content":{"type":"string","example":"Updated content"}}}}}},"responses":{"200":{"description":"Prompt updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt updated successfully."},"prompt":{"type":"object","properties":{"id":{"type":"integer","example":402},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"Updated Title"},"description":{"type":"string","example":"Desc"},"type":{"type":"string","example":"Other"},"content":{"type":"string","example":"Content"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"id is required."}}}}}},"404":{"description":"Prompt not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Prompt not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update prompt"}}}}}}}},"delete":{"tags":["App Builder"],"summary":"Delete component prompt","description":"Deletes a component prompt","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"query","required":true,"description":"Prompt ID","schema":{"type":"integer","example":402}}],"responses":{"200":{"description":"Prompt deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt deleted successfully."}}}}}},"400":{"description":"Bad request - missing or invalid id","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"id query parameter is required."}}}}}},"404":{"description":"Prompt not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Prompt not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to delete prompt"}}}}}}}}},"/app-builder/component-content":{"get":{"tags":["App Builder"],"summary":"Get component content items","description":"Retrieve all content items for a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","description":"Component ID","required":true,"schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Content items retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":501},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"app_verify_access"},"description":{"type":"string","example":"Loading text..."},"text":{"type":"string","example":"Verifying access..."},"format":{"type":"string","enum":["plain","markdown"],"example":"plain"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Body"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required and must be a valid number."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component content"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create component content item","description":"Create a new content item for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","textId","text","format","type"],"properties":{"componentId":{"type":"integer","example":201,"description":"Component ID"},"textId":{"type":"string","example":"welcome_msg","description":"Unique identifier for the content item"},"description":{"type":"string","example":"Welcome message","description":"Optional description of the content"},"text":{"type":"string","example":"Hello User!","description":"The actual content text"},"format":{"type":"string","enum":["plain","markdown"],"example":"plain","description":"Content format"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Body","description":"Content type"}}}}}},"responses":{"201":{"description":"Content item created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Content item created successfully."},"item":{"type":"object","properties":{"id":{"type":"integer","example":502},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"welcome_msg"},"description":{"type":"string","example":"Welcome message"},"text":{"type":"string","example":"Hello User!"},"format":{"type":"string","example":"plain"},"type":{"type":"string","example":"Body"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"textId is required and must be a non-empty string."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"409":{"description":"Conflict - content item with this textId already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item with this text_id already exists for this component"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create content item"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update component content item","description":"Update an existing content item. Only the fields provided in the request body will be updated.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":502,"description":"Content item ID"},"textId":{"type":"string","example":"updated_welcome_msg","description":"Optional - Updated text identifier"},"description":{"type":"string","example":"Updated description","description":"Optional - Updated description"},"text":{"type":"string","example":"Welcome back!","description":"Optional - Updated content text"},"format":{"type":"string","enum":["plain","markdown"],"example":"markdown","description":"Optional - Updated format"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Title","description":"Optional - Updated type"}}},"examples":{"Update text only":{"value":{"id":502,"text":"Updated welcome message"}},"Update multiple fields":{"value":{"id":502,"text":"Updated text","format":"markdown","type":"Heading"}}}}}},"responses":{"200":{"description":"Content item updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Content item updated successfully."},"item":{"type":"object","properties":{"id":{"type":"integer","example":502},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"welcome_msg"},"description":{"type":"string","example":"Updated description"},"text":{"type":"string","example":"Welcome back!"},"format":{"type":"string","example":"markdown"},"type":{"type":"string","example":"Title"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No valid fields provided for update. Allowed fields: textId, description, text, format, type"}}}}}},"404":{"description":"Content item not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item not found."}}}}}},"409":{"description":"Conflict - content item with this textId already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item with this text_id already exists for this component"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update content item"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/work-management/tasks":{"get":{"tags":["Work Management"],"summary":"Get tasks for authenticated user","description":"Retrieve all tasks assigned to the authenticated user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Tasks retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":601},"title":{"type":"string","example":"Complete Design Doc"},"description":{"type":"string","example":"Finalize the architecture diagram."},"assignee":{"type":"string","example":"User Name"},"assigned_date":{"type":"string","format":"date-time","example":"2025-01-01T10:00:00Z"},"due_date":{"type":"string","format":"date-time","example":"2025-01-05T17:00:00Z"},"start_date":{"type":"string","format":"date-time","nullable":true,"example":null},"close_date":{"type":"string","format":"date-time","nullable":true,"example":null},"status":{"type":"string","enum":["Assigned","Open","InProgress","Closed"],"example":"Assigned"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"401":{"description":"Unauthorized - user not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"User not authenticated."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch tasks"}}}}}}}},"post":{"tags":["Work Management"],"summary":"Create a new task","description":"Create a new task. The assignedDate is automatically set to the current timestamp.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","assignee","dueDate","organizationId","projectId"],"properties":{"title":{"type":"string","example":"New Task","description":"Task title"},"description":{"type":"string","example":"Task description","description":"Optional task description"},"assignee":{"type":"string","description":"User ID (uid) of the person assigned to this task","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"dueDate":{"type":"string","format":"date-time","example":"2025-01-10T17:00:00Z","description":"Task due date (ISO 8601 format)"},"organizationId":{"type":"integer","example":1,"description":"Organization ID"},"projectId":{"type":"integer","example":1,"description":"Project ID"}}}}}},"responses":{"201":{"description":"Task created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Task created successfully."},"task":{"type":"object","properties":{"id":{"type":"integer","example":602},"title":{"type":"string","example":"New Task"},"description":{"type":"string","example":"Desc"},"assignee":{"type":"string","example":"Assignee Name"},"assigned_date":{"type":"string","format":"date-time","example":"2025-01-02T12:00:00Z"},"due_date":{"type":"string","format":"date-time","example":"2025-01-10T17:00:00Z"},"start_date":{"type":"string","nullable":true,"example":null},"close_date":{"type":"string","nullable":true,"example":null},"status":{"type":"string","example":"Open"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required and must be a non-empty string."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create task"}}}}}}}}},"/work-management/tasks/{id}/status":{"patch":{"tags":["Work Management"],"summary":"Update task status","description":"Update the status of a task. Backend automatically sets startDate when status changes to InProgress and closeDate when status changes to Closed.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","description":"Task ID","required":true,"schema":{"type":"integer","example":601}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["Assigned","Open","InProgress","Closed"],"example":"InProgress","description":"New task status"}}},"examples":{"Start task":{"value":{"status":"InProgress"}},"Close task":{"value":{"status":"Closed"}},"Reopen task":{"value":{"status":"Open"}}}}}},"responses":{"200":{"description":"Task status updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Task status updated."},"task":{"type":"object","properties":{"id":{"type":"integer","example":601},"title":{"type":"string","example":"Complete Design Doc"},"description":{"type":"string","example":"Finalize the architecture diagram."},"assignee":{"type":"string","example":"User Name"},"assigned_date":{"type":"string","format":"date-time"},"due_date":{"type":"string","format":"date-time"},"start_date":{"type":"string","format":"date-time","example":"2025-01-03T09:00:00Z"},"close_date":{"type":"string","nullable":true,"example":null},"status":{"type":"string","example":"InProgress"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"status is required and must be one of: Open, InProgress, Closed"}}}}}},"404":{"description":"Task not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Task not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update task status"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/rbac/roles":{"get":{"tags":["RBAC"],"summary":"Get all roles","description":"Retrieve all roles in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Roles retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Admin"},"description":{"type":"string","example":"Administrator role"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Create a new role","description":"Create a new role in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"Editor"},"description":{"type":"string","example":"Can edit content"}}}}}},"responses":{"201":{"description":"Role created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Role created successfully."},"role":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}},"409":{"description":"Role already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Role with this name already exists."}}}}}}}}},"/rbac/roles/{roleId}":{"delete":{"tags":["RBAC"],"summary":"Delete a role","description":"Delete a role from the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Role deleted successfully"},"404":{"description":"Role not found"}}}},"/rbac/permissions":{"get":{"tags":["RBAC"],"summary":"Get all permissions","description":"Retrieve all permissions in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"create:component"},"description":{"type":"string","example":"Can create components"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Create a new permission","description":"Create a new permission in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"delete:component"},"description":{"type":"string","example":"Can delete components"}}}}}},"responses":{"201":{"description":"Permission created successfully"},"409":{"description":"Permission already exists"}}}},"/rbac/permissions/{permissionId}":{"delete":{"tags":["RBAC"],"summary":"Delete a permission","description":"Delete a permission from the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"permissionId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permission deleted successfully"},"404":{"description":"Permission not found"}}}},"/rbac/roles/{roleId}/permissions":{"get":{"tags":["RBAC"],"summary":"Get permissions for a role","description":"Retrieve all permissions assigned to a specific role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Add permission to role","description":"Assign a permission to a role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["permissionId"],"properties":{"permissionId":{"type":"integer","example":1}}}}}},"responses":{"200":{"description":"Permission added to role successfully"},"404":{"description":"Role or permission not found"}}}},"/rbac/roles/{roleId}/permissions/{permissionId}":{"delete":{"tags":["RBAC"],"summary":"Remove permission from role","description":"Remove a permission from a role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}},{"name":"permissionId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permission removed from role successfully"}}}},"/rbac/users/{userId}/roles":{"get":{"tags":["RBAC"],"summary":"Get user roles","description":"Retrieve all roles assigned to a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User roles retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Add user to role","description":"Assign a role to a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roleId"],"properties":{"roleId":{"type":"integer","example":1}}}}}},"responses":{"200":{"description":"User added to role successfully"},"404":{"description":"User or role not found"}}}},"/rbac/users/{userId}/roles/{roleId}":{"delete":{"tags":["RBAC"],"summary":"Remove user from role","description":"Remove a role from a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"User removed from role successfully"}}}},"/rbac/users/{userId}/permissions":{"get":{"tags":["RBAC"],"summary":"Get user permissions","description":"Retrieve all unique permissions for a user across all their roles","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}}},"/rbac/me/roles":{"get":{"tags":["RBAC"],"summary":"Get current user roles and permissions","description":"Retrieve role names and permission names for the currently authenticated user (from decrypted cookie)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current user roles and permissions retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string"},"example":["Admin","Editor"]},"permissions":{"type":"array","items":{"type":"string"},"example":["create:component","delete:component","edit:component"]}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/me/permissions":{"get":{"tags":["RBAC"],"summary":"Get current user permissions","description":"Retrieve all unique permissions for the currently authenticated user across all their roles (from decrypted cookie)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current user permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/me":{"get":{"tags":["RBAC"],"summary":"Get current user complete profile","description":"Retrieve roles, permissions, and preferences for the currently authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User profile retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string"},"example":["Admin","Editor"]},"permissions":{"type":"array","items":{"type":"string"},"example":["create:component","delete:component"]},"preferences":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string"},"app_id":{"type":"string"},"object_id":{"type":"string"},"object_type":{"type":"string"}}}}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/preferences":{"get":{"tags":["RBAC"],"summary":"Get all user preferences","description":"Retrieve all preferences for the currently authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Preferences retrieved successfully"}}},"post":{"tags":["RBAC"],"summary":"Save user preference","description":"Save or update a preference for the currently authenticated user","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appId"],"properties":{"appId":{"type":"string","example":"my-app"},"objectId":{"type":"string","example":"obj-123"},"objectType":{"type":"string","example":"dashboard"}}}}}},"responses":{"200":{"description":"Preference saved successfully"}}}},"/rbac/preferences/{appId}":{"get":{"tags":["RBAC"],"summary":"Get preference for specific app","description":"Retrieve preference for a specific app for the currently authenticated user","security":[{"BearerAuth":[]}],"parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Preference retrieved successfully"},"404":{"description":"Preference not found"}}}}}} \ No newline at end of file diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx new file mode 100644 index 0000000..f125d80 --- /dev/null +++ b/components/Dashboard.tsx @@ -0,0 +1,106 @@ + +import React, { useEffect, useState } from 'react'; +import { AppMetadata } from '../types'; +import { geminiService } from '../services/geminiService'; + +interface DashboardProps { + metadata: AppMetadata; +} + +export const Dashboard: React.FC = ({ metadata }) => { + const [greeting, setGreeting] = useState('Loading welcome message...'); + const [features, setFeatures] = useState([]); + const [isAiLoading, setIsAiLoading] = useState(true); + + useEffect(() => { + const initializeApp = async () => { + setIsAiLoading(true); + try { + const [aiGreeting, aiFeatures] = await Promise.all([ + geminiService.getAppContextualGreeting(metadata), + geminiService.getSmartFeatureIdeas(metadata) + ]); + setGreeting(aiGreeting); + setFeatures(aiFeatures); + } catch (error) { + console.error("AI Initialization failed", error); + setGreeting(`Welcome to ${metadata.name}`); + } finally { + setIsAiLoading(false); + } + }; + + initializeApp(); + }, [metadata]); + + return ( +
+
+
+
+

Identity

+

{metadata.name}

+

{metadata.description}

+
+
+ +
+
+

+ + + + + + Smart Assistant Greeting +

+
+ "{greeting}" +
+
+ +
+

+ + + + + + Dynamic Smart Features +

+
    + {isAiLoading ? ( + [1, 2, 3].map(i =>
  • ) + ) : ( + features.map((feature, idx) => ( +
  • +
    + {feature} +
  • + )) + )} +
+
+
+ +
+

Core Capabilities

+
+ {[ + { label: 'Data Tracking', icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z' }, + { label: 'Cloud Sync', icon: 'M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z' }, + { label: 'AI Optimization', icon: 'M13 10V3L4 14h7v7l9-11h-7z' }, + { label: 'Privacy First', icon: 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z' }, + ].map((item, idx) => ( +
+ + + +
{item.label}
+
+ ))} +
+
+
+ ); +}; diff --git a/components/Layout.tsx b/components/Layout.tsx new file mode 100644 index 0000000..126a38b --- /dev/null +++ b/components/Layout.tsx @@ -0,0 +1,43 @@ + +import React from 'react'; + +interface LayoutProps { + children: React.ReactNode; + title: string; +} + +export const Layout: React.FC = ({ children, title }) => { + return ( +
+
+
+
+
+
+ {title.charAt(0)} +
+

+ {title === 'NOTSET' ? 'App Template' : title} +

+
+ +
+
+
+
+
+ {children} +
+
+
+
+ Powered by Gemini AI & Dynamic Metadata Template +
+
+
+ ); +}; diff --git a/components/SetupPrompt.tsx b/components/SetupPrompt.tsx new file mode 100644 index 0000000..fc322d2 --- /dev/null +++ b/components/SetupPrompt.tsx @@ -0,0 +1,36 @@ + +import React from 'react'; + +export const SetupPrompt: React.FC = () => { + return ( +
+
+ + + +
+

Configuration Required

+

+ The application metadata is currently set to NOTSET. + Please update the metadata.json file with your app's name and description to proceed. +

+ +
+
+
+
+
+ metadata.json +
+
{`{
+  "name": "Your Awesome App",
+  "description": "A powerful tool to help users manage..."
+}`}
+
+ +

+ The AI will automatically generate features and interfaces based on your definition once you reload. +

+
+ ); +}; diff --git a/components/icons.tsx b/components/icons.tsx new file mode 100644 index 0000000..ccd00c9 --- /dev/null +++ b/components/icons.tsx @@ -0,0 +1,172 @@ + +import React from 'react'; + +export const SunIcon: React.FC = () => ( + + + +); + +export const MoonIcon: React.FC = () => ( + + + +); + +export const PaperAirplaneIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PaperClipIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const XMarkIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const GithubIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ExternalLinkIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CodeIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const EditIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const SparklesIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CheckCircleIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const EyeIcon: React.FC<{className?: string}> = ({ className }) => ( + + + + +); + +export const UploadIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const DownloadIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ClipboardIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ListBulletIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const DocumentTextIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PrinterIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CommandLineIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const UserPlusIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const FolderIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PlusIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChatBubbleLeftIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChevronRightIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChevronDownIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const TrashIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const BookOpenIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const Cog6ToothIcon: React.FC<{className?: string}> = ({ className }) => ( + + + + +); diff --git a/containers/MainContainer.tsx b/containers/MainContainer.tsx new file mode 100644 index 0000000..721998d --- /dev/null +++ b/containers/MainContainer.tsx @@ -0,0 +1,44 @@ +import React, { useState, useEffect } from 'react'; +import { useTheme } from '../contexts/ThemeContext'; +import { User } from '../types'; +import MainLayout from './layout/MainLayout'; + +interface MainContainerProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + children?: React.ReactNode; +} + +/** + * MainContainer handles layout logic: + * - Theme state access + * - App version fetching + */ +const MainContainer: React.FC = (props) => { + const { theme, toggleTheme } = useTheme(); + const [version, setVersion] = useState(null); + + useEffect(() => { + fetch('./version.json') + .then(response => { + if (response.ok) return response.json(); + return null; + }) + .then(data => { + if (data && data.version) setVersion(data.version); + }) + .catch(() => {}); + }, []); + + return ( + + ); +}; + +export default MainContainer; \ No newline at end of file diff --git a/containers/layout/MainLayout.tsx b/containers/layout/MainLayout.tsx new file mode 100644 index 0000000..74a67f5 --- /dev/null +++ b/containers/layout/MainLayout.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { User } from '../../types'; +import Header from './components/Header'; +import Footer from './components/Footer'; + +interface MainLayoutProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + theme: 'light' | 'dark'; + toggleTheme: () => void; + version: string | null; + children?: React.ReactNode; +} + +const MainLayout: React.FC = ({ + user, + workspaceUrl, + signOutUrl, + theme, + toggleTheme, + version, + children +}) => { + return ( +
+
+ +
+ {children || ( +
+

Framework Ready

+

The skeleton is successfully initialized.

+
+ )} +
+ +
+
+ ); +}; + +export default MainLayout; \ No newline at end of file diff --git a/containers/layout/components/Footer.tsx b/containers/layout/components/Footer.tsx new file mode 100644 index 0000000..86397c9 --- /dev/null +++ b/containers/layout/components/Footer.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +interface FooterProps { + version: string | null; +} + +const Footer: React.FC = ({ version }) => { + return ( +
+

© 2025 HumanizeIQ. All Rights Reserved. {version && `v${version}`}

+
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/containers/layout/components/Header.tsx b/containers/layout/components/Header.tsx new file mode 100644 index 0000000..b9cad23 --- /dev/null +++ b/containers/layout/components/Header.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { User } from '../../../types'; +import { SunIcon, MoonIcon } from '../../../components/icons'; + +interface HeaderProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + theme: 'light' | 'dark'; + toggleTheme: () => void; +} + +const Header: React.FC = ({ user, workspaceUrl, signOutUrl, theme, toggleTheme }) => { + return ( +
+
+ HumanizeIQ Logo +

Aura Craft Studio

+
+ + +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/content.json b/content.json new file mode 100644 index 0000000..883094f --- /dev/null +++ b/content.json @@ -0,0 +1,59 @@ + +[ + { + "textId": "app_verify_access", + "description": "Loading text while verifying authentication", + "text": "Verifying access...", + "format": "plain", + "type": "Body" + }, + { + "textId": "app_redirect_login", + "description": "Loading text while redirecting to login", + "text": "Redirecting to login...", + "format": "plain", + "type": "Body" + }, + { + "textId": "app_unauthorized_title", + "description": "Title for unauthorized access screen", + "text": "Unauthorized", + "format": "plain", + "type": "Title" + }, + { + "textId": "app_unauthorized_desc", + "description": "Description for unauthorized access screen", + "text": "You do not have permission to access this application.", + "format": "plain", + "type": "Body" + }, + { + "textId": "header_title", + "description": "Main application title in header", + "text": "Aura Craft Studio Template", + "format": "plain", + "type": "Title" + }, + { + "textId": "nav_workspace", + "description": "Navigation link to workspace", + "text": "My Workspace", + "format": "plain", + "type": "LinkText" + }, + { + "textId": "nav_signout", + "description": "Navigation link to sign out", + "text": "Sign out", + "format": "plain", + "type": "LinkText" + }, + { + "textId": "footer_copyright", + "description": "Footer copyright text", + "text": "© 2025 HumanizeIQ. All Rights Reserved.", + "format": "plain", + "type": "Body" + } +] diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx new file mode 100644 index 0000000..652ce14 --- /dev/null +++ b/contexts/AuthContext.tsx @@ -0,0 +1,51 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { User, Role, Permission } from '../types'; +import { useAuthSession } from '../hooks/useAuthSession'; + +interface AuthContextType { + authState: 'checking' | 'authorized' | 'unauthorized'; + user: User | null; + roles: Role[]; + permissions: Permission[]; + workspaceUrl: string; + signOutUrl: string; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const { authState, user, roles, permissions } = useAuthSession(); + const [workspaceUrl, setWorkspaceUrl] = useState('/home/workspace'); + const [signOutUrl, setSignOutUrl] = useState('/home/confirm-signout'); + + useEffect(() => { + const hostname = window.location.hostname; + if (hostname.startsWith('tools.')) { + const newHost = hostname.replace('tools.', 'www.'); + const protocol = window.location.protocol; + setWorkspaceUrl(`${protocol}//${newHost}/home/workspace`); + setSignOutUrl(`${protocol}//${newHost}/home/confirm-signout`); + } + }, []); + + return ( + + {children} + + ); +}; + +export const useAuthContext = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuthContext must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/contexts/ThemeContext.tsx b/contexts/ThemeContext.tsx new file mode 100644 index 0000000..342eab6 --- /dev/null +++ b/contexts/ThemeContext.tsx @@ -0,0 +1,42 @@ +import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import { useLocalStorage } from '../hooks/useLocalStorage'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [theme, setTheme] = useLocalStorage('theme', 'light'); + + useEffect(() => { + const root = window.document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + }, [theme]); + + const toggleTheme = () => { + setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; diff --git a/craft.txt b/craft.txt new file mode 100644 index 0000000..e69de29 diff --git a/features/workspace/WorkspaceContainer.tsx b/features/workspace/WorkspaceContainer.tsx new file mode 100644 index 0000000..c7e4a1b --- /dev/null +++ b/features/workspace/WorkspaceContainer.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react'; +import { useAuth } from '../../hooks/useAuth'; +import { isStudioMode } from '../../services/apiUtils'; +import MainContainer from '../../containers/MainContainer'; +import WorkspaceView from './WorkspaceView'; + +/** + * WorkspaceContainer handles the 'Smart' logic: + * - Authentication state monitoring + * - Redirections + * - Data fetching/preparation for the View + */ +const WorkspaceContainer: React.FC = () => { + const { authState, user, workspaceUrl, signOutUrl } = useAuth(); + + useEffect(() => { + if (authState === 'unauthorized' && !isStudioMode()) { + window.location.href = '/home/login'; + } + }, [authState]); + + if (authState === 'checking') { + return ( +
+
+

Verifying access...

+
+ ); + } + + if (authState === 'unauthorized' && isStudioMode()) { + return ( +
+
+

Unauthorized

+

You do not have permission to access this application.

+
+
+ ); + } + + return ( + + + + ); +}; + +export default WorkspaceContainer; \ No newline at end of file diff --git a/features/workspace/WorkspaceView.tsx b/features/workspace/WorkspaceView.tsx new file mode 100644 index 0000000..b1bc4e0 --- /dev/null +++ b/features/workspace/WorkspaceView.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { User } from '../../types'; +import StatusGrid from './components/StatusGrid'; + +interface WorkspaceViewProps { + user: User | null; +} + +/** + * WorkspaceView is a 'Dumb' component: + * - It only receives data via props. + * - It defines the visual layout and style. + */ +const WorkspaceView: React.FC = ({ user }) => { + return ( +
+
+ + + +
+ +

+ Workspace Ready +

+ +

+ The HumanizeIQ shared framework is initialized. Your authentication context and AI connection layer are fully wired up. +

+ + +
+ ); +}; + +export default WorkspaceView; \ No newline at end of file diff --git a/features/workspace/components/StatusCard.tsx b/features/workspace/components/StatusCard.tsx new file mode 100644 index 0000000..9d3f709 --- /dev/null +++ b/features/workspace/components/StatusCard.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +interface StatusCardProps { + label: string; + value: string; + colorClass: string; +} + +const StatusCard: React.FC = ({ label, value, colorClass }) => { + return ( +
+

+ {label} +

+

+ {value} +

+
+ ); +}; + +export default StatusCard; \ No newline at end of file diff --git a/features/workspace/components/StatusGrid.tsx b/features/workspace/components/StatusGrid.tsx new file mode 100644 index 0000000..05bb844 --- /dev/null +++ b/features/workspace/components/StatusGrid.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { User } from '../../../types'; +import StatusCard from './StatusCard'; + +interface StatusGridProps { + user: User | null; +} + +const StatusGrid: React.FC = ({ user }) => { + return ( +
+ + + +
+ ); +}; + +export default StatusGrid; \ No newline at end of file diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts new file mode 100644 index 0000000..2512135 --- /dev/null +++ b/hooks/useAuth.ts @@ -0,0 +1,7 @@ + +import { useAuthContext } from '../contexts/AuthContext'; + +// Export as useAuth to maintain backward compatibility +export const useAuth = () => { + return useAuthContext(); +}; diff --git a/hooks/useAuthSession.ts b/hooks/useAuthSession.ts new file mode 100644 index 0000000..39c18fe --- /dev/null +++ b/hooks/useAuthSession.ts @@ -0,0 +1,62 @@ +import { useState, useEffect } from 'react'; +import { User, Role, Permission } from '../types'; +import * as appBuilderService from '../services/appBuilderService'; + +export const useAuthSession = () => { + const [authState, setAuthState] = useState<'checking' | 'authorized' | 'unauthorized'>('checking'); + const [user, setUser] = useState(null); + const [roles, setRoles] = useState([]); + const [permissions, setPermissions] = useState([]); + + useEffect(() => { + const checkAuth = async () => { + const isStudio = window.location.href.includes('.goog'); + const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; + + // Configure Gemini API Key globally for compatibility + (window as any).GEMINI_API_KEY = isStudio + ? ((window as any).process?.env?.API_KEY || 'NOTFOUND') + : 'NOT_SET'; + + if (isLocal) { + setUser({ name: 'Local Dev User', company_name: 'HumanizeIQ', uid: 'local-dev-uid' }); + setAuthState('authorized'); + setRoles(['Admin']); + setPermissions(['all:access']); + return; + } + + const authUrl = isGoogDomain ? 'https://www.playtest.humanizeiq.ai/auth/ai_studio' : '/auth/ai_studio'; + + try { + const fetchOptions: RequestInit = { credentials: 'include' }; + + const response = await fetch(authUrl, fetchOptions); + if (response.status === 200) { + const result = await response.json(); + if (result.data?.firstname) { + setUser({ + name: `${result.data.firstname} ${result.data.lastname}`, + company_name: result.data.company_name, + auth_cookie: result.data.auth_cookie_base64, + uid: result.data.uid + }); + setAuthState('authorized'); + const rbacData = await appBuilderService.getMyRbacDetails().catch(() => ({ roles: [], permissions: [] })); + setRoles(rbacData.roles); + setPermissions(rbacData.permissions); + } else { + setAuthState('unauthorized'); + } + } else { + setAuthState('unauthorized'); + } + } catch (error) { + setAuthState('unauthorized'); + } + }; + checkAuth(); + }, []); + + return { authState, user, roles, permissions }; +}; \ No newline at end of file diff --git a/hooks/useLocalStorage.ts b/hooks/useLocalStorage.ts new file mode 100644 index 0000000..cca7c4d --- /dev/null +++ b/hooks/useLocalStorage.ts @@ -0,0 +1,31 @@ +// FIX: Import React to make the 'React' namespace available for type annotations. +import React, { useState, useEffect } from 'react'; + +export function useLocalStorage(key: string, initialValue: T): [T, React.Dispatch>] { + const [storedValue, setStoredValue] = useState(() => { + if (typeof window === 'undefined') { + return initialValue; + } + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + console.error(error); + return initialValue; + } + }); + + useEffect(() => { + try { + const valueToStore = + typeof storedValue === 'function' + ? storedValue(storedValue) + : storedValue; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + console.error(error); + } + }, [key, storedValue]); + + return [storedValue, setStoredValue]; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..2ec10b3 --- /dev/null +++ b/index.html @@ -0,0 +1,57 @@ + + + + + + + Aura Craft Studio Template + + + + + + + +
+ + + + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..7f563ac --- /dev/null +++ b/index.tsx @@ -0,0 +1,22 @@ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { ThemeProvider } from './contexts/ThemeContext'; +import { AuthProvider } from './contexts/AuthContext'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + + + + + +); diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..86264e4 --- /dev/null +++ b/instructions.md @@ -0,0 +1,43 @@ + +# 🚀 Getting Started with Aura Craft Studio + +This template is a pre-wired, modular framework designed for AI-accelerated development. Follow these two steps to build your application. + +## 1️⃣ Step 1: Manual Setup +Before using AI prompts, manually update `metadata.json` in the root directory. This tells the system who you are and what you are building. + +```json +{ + "name": "Your App Name", + "description": "Clear description of your app's purpose", + "organization": "YourOrg", + "project": "YourProject", + "component": "YourApp" +} +``` +*Note: The `organization`, `project`, and `component` fields are critical for the AI to fetch your specific system instructions and prompts from the CMS.* + +## 2️⃣ Step 2: AI-Driven Development +From this point forward, use the **Mandatory Prompt Template** below for every change or feature request. This ensures the AI respects the modular architecture and safety rules. + +### 📋 Mandatory Prompt Template +Copy and paste this into your prompt when asking for updates: + +> "I want to [describe your request]. +> +> **MANDATORY CONSTRAINTS:** +> 1. **STRICT ADHERENCE TO rules.md**: Read and follow all rules in `rules.md` without exception. +> 2. **CONTAINER/VIEW PATTERN**: Separate logic into smart `*Container.tsx` files and UI into presentational `*View.tsx` files. +> 3. **200-LINE LIMIT**: No single file may exceed 200 lines of code. Decompose into smaller modules or custom hooks if necessary. +> 4. **WIRING PROTECTION**: Do not modify core infrastructure files (Auth, API Utils, Gemini Service). +> 5. **AESTHETICS**: Use Tailwind CSS with full dark mode support (`dark:` variants) and responsive prefixes. +> 6. **MODULARITY**: Group new features under `features/[feature-name]/`." + +--- + +## 🛠️ Developer Reference (For the AI) +- **`useAuth()`**: Access user identity and RBAC permissions. +- **`useTheme()`**: Toggle between light and dark modes. +- **`generateResponse()`**: Call Gemini via the HumanizeIQ proxy without managing keys. +- **`apiService.ts`**: Standardized R2 cloud storage operations (Upload/Download/List). +- **`rules.md`**: The source of truth for all architectural constraints. diff --git a/local_cookie.json b/local_cookie.json new file mode 100644 index 0000000..7bad626 --- /dev/null +++ b/local_cookie.json @@ -0,0 +1,4 @@ +{ + "cookie": "VTJGc2RHVmtYMS9sVTJ5MGhJYmR0TFFOekQzRStwbnhRWmF0cDRDYndPYlZZQ1prcmpOWWtIWlBKNUx2a0lIQjAwWGczNlhFSktLV0lERjl2U0NXZDd5MG1XL0t3bTlmZmZseTN6N29tbTdmVGR2YWlRT2o3b2ZKM0RQZjEyWTVxMjJzNFBJdjlidjBLcGlhaUVWY3IrSFhzZHAwU3A3bGh6Sjlsdnd5VzlCN3BtRnVRWUhYSDUwUmt2NloreVBaQ0pnV2E4YmovT3hjRDdXM2JMNHlkeTd4QWZBbFE4OE9DaWcyREtIUHQ3aEpDeG4reGw0UG5IT3pmOG9ndFJoekViUHN0SGlLWUNhanRXSWxha0UwRVhwNllmSHBDOVlPdWEyWkk0YWJyY084djRjMDFJUWMwZXpkWEtNY05hamhqZjlkSXV5eWFjekRVMFdYYzZQZU53PT0=" +} + diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..aa6ffcc --- /dev/null +++ b/metadata.json @@ -0,0 +1,8 @@ +{ + "name": "NOTSET", + "description": "A reusable UI skeleton with integrated Authentication, Theme management, and AI Connection services.", + "requestFramePermissions": [], + "organization": "HumanizeIQ", + "project": "Templates", + "component": "Frontend Template" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..0cec2ed --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "securechat", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0", + "@google/genai": "^1.24.0", + "openai": "4.28.0", + "pdfjs-dist": "3.11.174", + "mammoth": "1.6.0", + "xlsx": "0.18.5", + "jspdf": "2.5.1" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/public/version.json b/public/version.json new file mode 100644 index 0000000..b8fbae9 --- /dev/null +++ b/public/version.json @@ -0,0 +1 @@ +{"version":"1.0.0"} diff --git a/rules.md b/rules.md new file mode 100644 index 0000000..7c3d2dc --- /dev/null +++ b/rules.md @@ -0,0 +1,35 @@ +# 📜 HumanizeIQ AI Development Rules + +This document outlines the strict boundaries and guidelines for AI-driven modifications to this application. These rules ensure that the **"Wiring"** (Auth, API Proxies, RBAC) and **"Branding"** (Standard Layout) remain functional and consistent across all Aura Craft Studio projects. + +## 🚫 1. Immutable "Wiring" (DO NOT MODIFY) +The following files and logic sections are core infrastructure. Modification may break connectivity with the HumanizeIQ backend or Google AI Studio. + +- **`services/apiUtils.ts`**: Logic for switching between Studio and Deployed modes. +- **`services/geminiService.ts`**: The proxy-aware AI client initialization. +- **`services/apiService.ts`**: R2 storage integration methods. +- **`metadata.json`**: The application's DNA. +- **`local_cookie.json`**: Essential for authentication within Google AI Studio. + +## 🏗️ 2. Architecture: Container/View & SRP +To maintain scalability and the Single Responsibility Principle, follow these patterns: + +- **Logic vs. Display Separation**: Business logic, data fetching, and state orchestration must live in **Containers** (`*Container.tsx`) or **Hooks** (`use*.ts`). UI layout and styling must live in **Views** (`*View.tsx` or `*Layout.tsx`). +- **Max File Length**: No code file should exceed **200 lines**. If a file grows beyond this, it MUST be decomposed into smaller, more specific modules, sub-components, or custom hooks. +- **Modular Features**: New capabilities should be grouped under `features/[feature-name]/` with their own logic and display files. + +## ✅ 3. Permitted Modification Areas +- **`features/`**: Create domain-specific modules. +- **`components/`**: Create shared UI atoms or molecules. +- **`types.ts`**: Add new interfaces specific to the application's domain. +- **`hooks/`**: Add custom React hooks for local state or specific data fetching. + +## 🎨 4. Aesthetic & Theme Standards +- **Tailwind Strategy**: Always use Tailwind CSS classes. Avoid inline styles. +- **Dark Mode**: Every component **must** include `dark:` variants. +- **Responsiveness**: Ensure layouts are mobile-friendly using `sm:`, `md:`, and `lg:` prefixes. + +## 🤖 5. Integration Best Practices +- **Data Fetching**: Use `getUrlWithStudioAuth` and `getFetchOptions` from `apiUtils.ts`. +- **AI Prompts**: Use `getSystemInstruction(key)` from `geminiService.ts`. +- **Error Handling**: Implement loading states and graceful fallbacks. \ No newline at end of file diff --git a/services/apiService.ts b/services/apiService.ts new file mode 100644 index 0000000..ff3efe5 --- /dev/null +++ b/services/apiService.ts @@ -0,0 +1,160 @@ + +import { getUrlWithStudioAuth, getFetchOptions } from './apiUtils'; + +const getApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + + if (isStudioMode) { + // In Studio Mode, use the absolute URL for the dev environment. + return 'https://www.playtest.humanizeiq.ai/api/r2-explorer'; + } + + // In deployed environments (dev, prod, local), use a relative URL. + return '/api/r2-explorer'; +}; + +export async function uploadFile(file: File, metadata: object): Promise { + const baseUrl = `${getApiBaseUrl()}/upload`; + const url = await getUrlWithStudioAuth(baseUrl); + + const formData = new FormData(); + formData.append('file', file); + formData.append('metadata', JSON.stringify(metadata)); + + const options = await getFetchOptions({ + method: 'POST', + body: formData, + }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`File upload failed with status ${response.status}: ${errorText}`); + try { + error.body = JSON.parse(errorText); + } catch (e) { + error.body = { error: errorText }; + } + throw error; + } + return await response.json(); + } catch (error) { + console.error('Error uploading file:', error); + throw error; + } +} + +export async function updateFile(file: File, fileIdOrPath: string, isPath: boolean = false, metadata?: object): Promise { + const baseUrl = `${getApiBaseUrl()}/update-file`; + const url = await getUrlWithStudioAuth(baseUrl); + + // Construct query params carefully to preserve existing auth params + const separator = url.includes('?') ? '&' : '?'; + const paramName = isPath ? 'path' : 'fileId'; + const fetchUrl = `${url}${separator}${paramName}=${encodeURIComponent(fileIdOrPath)}`; + + const formData = new FormData(); + formData.append('file', file); + if (metadata) { + formData.append('metadata', JSON.stringify(metadata)); + } + + const options = await getFetchOptions({ + method: 'PUT', + body: formData, + }); + + try { + const response = await fetch(fetchUrl, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`File update failed with status ${response.status}: ${errorText}`); + try { + error.body = JSON.parse(errorText); + } catch (e) { + error.body = { error: errorText }; + } + throw error; + } + return await response.json(); + } catch (error) { + console.error('Error updating file:', error); + throw error; + } +} + +export async function listFiles(category?: string): Promise { + const baseUrl = `${getApiBaseUrl()}/files`; + let url = await getUrlWithStudioAuth(baseUrl); + + if (category) { + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}category=${encodeURIComponent(category)}`; + } + + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to list files: ${response.status} ${errorText}`); + } + return await response.json(); +} + +export async function listFilesByMetadata(metadata: any): Promise { + const baseUrl = `${getApiBaseUrl()}/files-by-metadata`; + const url = await getUrlWithStudioAuth(baseUrl); + + const options = await getFetchOptions({ + method: 'POST', + body: JSON.stringify({ metadata }) + }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to list files by metadata: ${response.status} ${errorText}`); + } + return await response.json(); + } catch (error) { + console.error('Error listing files by metadata:', error); + throw error; + } +} + +export async function downloadFile(pathOrId: string, isPath: boolean = true): Promise { + const baseUrl = `${getApiBaseUrl()}/download-file`; + let url = await getUrlWithStudioAuth(baseUrl); + + const separator = url.includes('?') ? '&' : '?'; + const param = isPath ? 'path' : 'fileId'; + url = `${url}${separator}${param}=${encodeURIComponent(pathOrId)}`; + + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to download file: ${response.status} ${errorText}`); + } + // Return JSON directly as we are storing JSON files + return await response.json(); +} + +export async function deleteFile(pathOrId: string, isPath: boolean = true): Promise { + const baseUrl = `${getApiBaseUrl()}/delete-file`; + let url = await getUrlWithStudioAuth(baseUrl); + + const separator = url.includes('?') ? '&' : '?'; + const param = isPath ? 'path' : 'fileId'; + url = `${url}${separator}${param}=${encodeURIComponent(pathOrId)}`; + + const options = await getFetchOptions({ method: 'DELETE' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to delete file: ${response.status} ${errorText}`); + } + return await response.json(); +} diff --git a/services/apiUtils.ts b/services/apiUtils.ts new file mode 100644 index 0000000..27118ca --- /dev/null +++ b/services/apiUtils.ts @@ -0,0 +1,87 @@ + +/** + * Shared utility functions for making authenticated API calls. + * Handles logic for both Studio Mode and Deployed Mode. + */ + +export const isStudioMode = (): boolean => { + const hostname = window.location.href; + return hostname.includes('.goog'); +}; + +// --- Studio Mode Cookie Loading (Async) --- +// This promise ensures the cookie is fetched only once per session. +let studioCookiePromise: Promise | null = null; + +export const fetchStudioCookie = (): Promise => { + if (!isStudioMode()) { + return Promise.resolve(null); + } + if (studioCookiePromise) { + return studioCookiePromise; + } + studioCookiePromise = (async () => { + try { + // Fetch from the application's relative path + const response = await fetch('./local_cookie.json'); + if (!response.ok) { + console.error(`Failed to fetch local_cookie.json: ${response.statusText}`); + return null; + } + const data = await response.json(); + if (data && typeof data.cookie === 'string') { + return data.cookie; + } + console.error("Invalid format for local_cookie.json. Expected { \"cookie\": \"...\" }"); + return null; + } catch (error) { + console.error("Error fetching or parsing local_cookie.json:", error); + return null; + } + })(); + return studioCookiePromise; +}; +// --- End Studio Mode Cookie Loading --- + +/** + * Appends the Studio Mode authentication cookie as a query parameter to a URL if needed. + * @param baseUrl The base URL for the API call. + * @returns The URL with the auth parameter if in Studio Mode. + */ +export const getUrlWithStudioAuth = async (baseUrl: string): Promise => { + if (!isStudioMode()) { + return baseUrl; + } + const cookie = await fetchStudioCookie(); + if (!cookie) { + return baseUrl; + } + const param = `X-Studio-Cookie=${encodeURIComponent(cookie)}`; + if (baseUrl.includes('?')) { + return `${baseUrl}&${param}`; + } else { + return `${baseUrl}?${param}`; + } +} + +/** + * Creates the options object for a fetch call, including credentials and headers. + * @param options Initial RequestInit options. + * @returns A complete RequestInit object for the fetch call. + */ +export const getFetchOptions = async (options: RequestInit = {}): Promise => { + const headers = new Headers(options.headers); + + // For FormData, let the browser set the Content-Type with the correct boundary. + // For other POST/PUT/PATCH requests, default to application/json if not set. + if (!(options.body instanceof FormData) && options.method && ['POST', 'PUT', 'PATCH'].includes(options.method.toUpperCase()) && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/json'); + } + + // Studio auth is handled via query parameter, but 'credentials: include' is still needed for deployed mode cookies. + return { + ...options, + credentials: 'include', + headers: headers, + }; +}; diff --git a/services/appBuilder/componentService.ts b/services/appBuilder/componentService.ts new file mode 100644 index 0000000..01ce1b9 --- /dev/null +++ b/services/appBuilder/componentService.ts @@ -0,0 +1,122 @@ + +import type { ProjectComponent } from '../../types'; +import { isStudioMode, getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; + +// Helper to resolve the App Builder's own component based on metadata.json +let selfComponentPromise: Promise | null = null; + +// Re-implementing simplified getOrganizations/getProjects/getComponents locally to avoid circular dependencies or importing unused modules +// purely for getSelfComponent resolution logic if needed, or we can assume metadata is correct. +// However, the original implementation relied on fetching from DB to confirm. +// We will keep minimal fetch logic for getSelfComponent resolution. + +const fetchComponentsMinimal = async (projectId: number): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/components`; + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('projectId', projectId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data.map((c: any) => ({...c, projectId: c.projectId || c.project_id})) : []; +}; + +const fetchOrganizationsMinimal = async (): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/organizations`; + const url = await getUrlWithStudioAuth(baseUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + return response.ok ? await response.json() : []; +}; + +const fetchProjectsMinimal = async (orgId: number): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/projects`; + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('organizationId', orgId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + return response.ok ? await response.json() : []; +}; + +export const getSelfComponent = async (): Promise => { + if (selfComponentPromise) return selfComponentPromise; + + selfComponentPromise = (async () => { + try { + console.log("getSelfComponent: Starting lookup..."); + let url: string; + // Use document.baseURI to correctly locate metadata.json in both Studio and Deployed modes + if (isStudioMode()) { + const base = document.baseURI.endsWith('/') ? document.baseURI : `${document.baseURI}/`; + url = new URL('metadata.json', base).href; + } else { + // Calculate base: Host + first path segment (ignoring index.html) + const pathParts = window.location.pathname.split('/').filter(p => p && p !== 'index.html'); + const base = pathParts.length > 0 + ? `${window.location.origin}/${pathParts[0]}/` + : `${window.location.origin}/`; + url = new URL('metadata.json', base).href; + } + + const metaRes = await fetch(url); + + if (!metaRes.ok) { + console.warn(`getSelfComponent: Could not fetch metadata.json. Status: ${metaRes.status}`); + return null; + } + const metadata = await metaRes.json(); + console.log("getSelfComponent: Metadata loaded:", metadata); + + const { organization, project, component } = metadata; + + if (!organization || !project || !component) { + console.warn("getSelfComponent: metadata.json missing required fields (organization, project, component)"); + return null; + } + + // 1. Find Org + const orgs = await fetchOrganizationsMinimal(); + const orgObj = orgs.find(o => o.name === organization); + if (!orgObj) { + console.warn(`getSelfComponent: Organization '${organization}' not found in DB.`); + return null; + } + + // 2. Find Project + const projects = await fetchProjectsMinimal(orgObj.id); + const projObj = projects.find(p => p.name === project); + if (!projObj) { + console.warn(`getSelfComponent: Project '${project}' not found in org '${organization}'.`); + return null; + } + + // 3. Find Component + const components = await fetchComponentsMinimal(projObj.id); + // Match by name or title + const compObj = components.find(c => c.name === component || c.title === component); + if (!compObj) { + console.warn(`getSelfComponent: Component '${component}' not found in project '${project}'.`); + return null; + } + + console.log(`getSelfComponent: Resolved Self Component: ${compObj.id} for ${organization}/${project}/${component}`); + return compObj; + + } catch (e) { + console.error("getSelfComponent: Failed to resolve self component", e); + return null; + } + })(); + + return selfComponentPromise; +}; + +export const getSelfComponentId = async (): Promise => { + const comp = await getSelfComponent(); + return comp ? comp.id : null; +}; diff --git a/services/appBuilder/config.ts b/services/appBuilder/config.ts new file mode 100644 index 0000000..fb18de0 --- /dev/null +++ b/services/appBuilder/config.ts @@ -0,0 +1,21 @@ + +export const getAppBuilderApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + if (isStudioMode) { + // Management APIs (RBAC, Auth) in the HumanizeIQ ecosystem usually sit under this segment + return 'https://www.playtest.humanizeiq.ai/api/ai_studio_manager_api'; + } else { + // In deployed mode, use a relative path. + return '/api/ai_studio_manager_api'; + } +}; +export const getUserManagementApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + if (isStudioMode) { + // Management APIs (RBAC, Auth) in the HumanizeIQ ecosystem usually sit under this segment + return 'https://www.playtest.humanizeiq.ai/api/user_management'; + } else { + // In deployed mode, use a relative path. + return '/api/user_management'; + } +}; \ No newline at end of file diff --git a/services/appBuilder/contentService.ts b/services/appBuilder/contentService.ts new file mode 100644 index 0000000..7c645e4 --- /dev/null +++ b/services/appBuilder/contentService.ts @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/services/appBuilder/promptService.ts b/services/appBuilder/promptService.ts new file mode 100644 index 0000000..e17aa2c --- /dev/null +++ b/services/appBuilder/promptService.ts @@ -0,0 +1,84 @@ + +import type { ComponentPrompt } from '../../types'; +import { getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; +import { getSelfComponentId } from './componentService'; + +/** + * Retrieves prompts for a specific component. + * @param componentId The ID of the component. + */ +export const getComponentPrompts = async (componentId: number): Promise => { + console.log(`API: Fetching prompts for componentId ${componentId}`); + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/component-prompts`; + + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('componentId', componentId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`API call failed with status ${response.status}: ${errorText}`); + error.status = response.status; + try { error.body = JSON.parse(errorText); } catch (e) { error.body = errorText; } + throw error; + } + return await response.json(); + } catch (error: any) { + console.error(`Error fetching prompts for component ${componentId}:`, error); + throw error; + } +}; + +/** + * Helper to convert prompt array to record map + */ +const convertPromptsToMap = (prompts: ComponentPrompt[]): Record => { + const map: Record = {}; + if (Array.isArray(prompts)) { + prompts.forEach(p => { + if (p && p.title) { + map[p.title] = p.content; + } + }); + } + return map; +}; + +/** + * Retrieves prompts for a specific component as a key-value map. + * This is useful for easy lookup by prompt title. + */ +export const getPrompts = async (componentId: number): Promise> => { + try { + const prompts = await getComponentPrompts(componentId); + return convertPromptsToMap(prompts); + } catch (e) { + console.error(`Failed to get prompts for component ${componentId}`, e); + return {}; + } +} + +/** + * Retrieves system prompts for the current application (Self Component). + * Returns a map of prompt title to prompt content. + */ +export const getSystemPrompts = async (): Promise> => { + try { + const selfId = await getSelfComponentId(); + if (!selfId) { + console.warn("getSystemPrompts: No self component ID found."); + return {}; + } + const prompts = await getComponentPrompts(selfId); + return convertPromptsToMap(prompts); + } catch (e) { + console.error("Failed to get system prompts", e); + return {}; + } +} diff --git a/services/appBuilder/rbacService.ts b/services/appBuilder/rbacService.ts new file mode 100644 index 0000000..952fbaa --- /dev/null +++ b/services/appBuilder/rbacService.ts @@ -0,0 +1,31 @@ + +import type { Role, Permission } from '../../types'; +import { getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; + +/** + * Retrieves the current user's roles and permissions from the RBAC endpoint. + * @returns A promise that resolves with an object containing arrays of roles and permissions. + */ +export const getMyRbacDetails = async (): Promise<{ roles: Role[], permissions: Permission[] }> => { + console.log('API: Fetching RBAC details (roles and permissions)'); + const baseUrl = `${getAppBuilderApiBaseUrl()}/rbac/me`; + const url = await getUrlWithStudioAuth(baseUrl); + const options = await getFetchOptions({ method: 'GET' }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to fetch RBAC details: ${response.status} ${errorText}`); + } + const data = await response.json(); + return { + roles: Array.isArray(data.roles) ? data.roles : [], + permissions: Array.isArray(data.permissions) ? data.permissions : [] + }; + } catch (error: any) { + console.error('Error fetching user RBAC details:', error); + throw error; + } +}; diff --git a/services/appBuilderService.ts b/services/appBuilderService.ts new file mode 100644 index 0000000..bca7408 --- /dev/null +++ b/services/appBuilderService.ts @@ -0,0 +1,8 @@ + +// This file re-exports all functionalities from the modular services. +// This maintains backward compatibility with existing imports. + +export * from './appBuilder/config'; +export * from './appBuilder/componentService'; +export * from './appBuilder/promptService'; +export * from './appBuilder/rbacService'; diff --git a/services/geminiService.ts b/services/geminiService.ts new file mode 100644 index 0000000..41794d2 --- /dev/null +++ b/services/geminiService.ts @@ -0,0 +1,54 @@ + +import { GoogleGenAI } from "@google/genai"; +import { getSystemPrompts } from './appBuilderService'; +import { fetchStudioCookie } from './apiUtils' + +/** + * Returns a configured GoogleGenAI instance. + * Per @google/genai guidelines, it strictly uses process.env.API_KEY. + */ +export const getAi = async () => { + // The API key must be obtained exclusively from process.env.API_KEY + const apiKey = process.env.API_KEY || 'NOT_FOUND'; + + const href = window.location.href; + const hostname = window.location.hostname; + const isStudioMode = href.includes('.goog'); + const isLocal = hostname === 'localhost' || hostname === '127.0.0.1'; + + /** + * HumanizeIQ specific: Non-studio modes (deployed apps) route through a proxy + * for unified access control. + */ + let baseUrl='https://www.playtest.humanizeiq.ai/api-proxy' + if (!isStudioMode) { + baseUrl = `${window.location.origin}/api-proxy`; + } + const headers: Record = { + 'User-Agent': 'DraftingStudio' + }; + if (isStudioMode) { + const cookie = await fetchStudioCookie(); + if (cookie) { + headers['X-Studio-Cookie'] = cookie; + } + } + + return new GoogleGenAI({ + apiKey: apiKey, + httpOptions: { + baseUrl: baseUrl, + headers: headers + } + }); +}; + +// Cache for system prompts to avoid repeated fetches +let systemPromptsCache: Record | null = null; + +export const getSystemInstruction = async (key: string): Promise => { + if (!systemPromptsCache) { + systemPromptsCache = await getSystemPrompts(); + } + return systemPromptsCache[key] || ''; +}; diff --git a/services/llmService.ts b/services/llmService.ts new file mode 100644 index 0000000..aeff2bb --- /dev/null +++ b/services/llmService.ts @@ -0,0 +1,89 @@ + + +import { getAi } from './geminiService'; +import { isStudioMode, fetchStudioCookie } from './apiUtils'; +import OpenAI from 'openai'; +import type { ChatMessage, ModelProvider, LLMConfig } from '../types'; + +export const MODELS: Record = { + openai: ['chatgpt-latest'], + // Use the latest recommended model for basic text tasks + google: ['gemini-3-flash-preview'] +}; + +export const generateResponse = async ( + config: LLMConfig, + messages: ChatMessage[] +): Promise => { + // Prepare headers for authentication (needed for proxy in Studio Mode) + const headers: Record = {}; + if (isStudioMode()) { + const cookie = await fetchStudioCookie(); + if (cookie) { + headers['X-Studio-Cookie'] = cookie; + } + } + + if (config.provider === 'google') { + const ai = await getAi(); + const history = messages.slice(0, -1).map(m => ({ + role: m.role, + parts: [{ text: m.context || m.content }] + })); + + const lastMessage = messages[messages.length - 1]; + const content = lastMessage.context || lastMessage.content; + + const chatConfig: any = { + model: config.model, + history: history, + }; + + if (config.systemInstruction) { + chatConfig.config = { + systemInstruction: config.systemInstruction + }; + } + + const chat = ai.chats.create(chatConfig); + + const response = await chat.sendMessage({ message: content }); + // Correct usage of .text property + return response.text || "No response text."; + + } else if (config.provider === 'openai') { + // API Key handled by proxy + const apiKey = config.apiKey || 'managed-by-proxy'; + + const baseURL = 'https://www.playtest.humanizeiq.ai/api-proxy/openai/v1'; + + const openai = new OpenAI({ + apiKey: apiKey, + baseURL: baseURL, + dangerouslyAllowBrowser: true, + defaultHeaders: headers + }); + + let openAiMessages = messages.map(m => ({ + role: m.role === 'model' ? 'assistant' : 'user', + content: m.context || m.content + })) as any[]; + + if (config.systemInstruction) { + openAiMessages = [ + { role: 'system', content: config.systemInstruction }, + ...openAiMessages + ]; + } + + const completion = await openai.chat.completions.create({ + messages: openAiMessages, + model: config.model, + }); + + return completion.choices[0]?.message?.content || "No response text."; + + } + + throw new Error(`Unsupported provider: ${config.provider}`); +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..0fa6cf7 --- /dev/null +++ b/types.ts @@ -0,0 +1,173 @@ + + +export interface ChatMessage { + role: 'user' | 'model'; + content: string; + context?: string; // Stores full content (e.g., with file attachments) for AI context, while content is for display +} + +export type LogStatus = 'pending' | 'success' | 'error' | 'info'; + +export interface LogMessage { + id: number; + message: string; + status: LogStatus; +} + +export interface User { + name: string; + company_name?: string; + auth_cookie?: string; + uid?: string; +} + +export interface UserSettings { + systemPrompt: string; +} + +export type Role = string; + +export type Permission = string; + +export interface AppInfo { + id: number; + name: string; + createdBy: string; + primary_domain?: string; + base_url?: string; + studio_app_url?: string; +} + +export interface PullRequest { + id: number; + number?: number; + title: string; + state: 'open' | 'closed'; + user: { + login: string; + avatar_url: string; + }; + head: string | { + ref: string; + }; + base: string | { + ref: string; + }; + html_url: string; + created_at: string; + updated_at?: string; + merged: boolean; + merged_at?: string | null; + source?: 'gitea' | 'database'; + environment?: 'non-prod' | 'prod' | 'dev'; + envName?: string; + hasConflicts?: boolean; +} + +export interface Organization { + id: number; + name: string; + description?: string; + createdAt?: string; +} + +export interface Project { + id: number; + name: string; + description?: string; + organizationId: number; + createdAt?: string; +} + +export interface ProjectComponent { + id: number; + projectId: number; + name: string; + title: string; + type: 'UX' | 'API'; + description?: string; + status?: 'Pending' | 'Active' | 'Inactive'; + createdAt?: string; + additional_info?: { + slug?: string; + supported_domains?: string[]; + ai_studio_link?: string; + github_repo?: string; + github_owner?: string; + hiq_repo?: string; + unique_app_code?: string; + [key: string]: any; + }; +} + +export type RequirementType = 'Defect' | 'Feature'; +export type RequirementCategory = 'User' | 'System' | 'Non-Functional'; +// Updated to match backend API validation +export type RequirementStatus = 'New' | 'Open' | 'InProgress' | 'Resolved' | 'Closed'; + +export interface Requirement { + id?: number; + componentId?: number; + type: RequirementType; + category?: RequirementCategory; + title: string; + description: string; + status: RequirementStatus; + tempId?: string; // For frontend tracking of new items before they have a DB ID + unique_hash?: string; +} + +export interface ComponentPrompt { + id?: number; + componentId: number; + title: string; + description: string; + type: 'System' | 'Other'; + content: string; + createdAt?: string; + updatedAt?: string; +} + +export interface ComponentContentItem { + id?: number; + componentId: number; + textId: string; + description: string; + text: string; + format: 'plain' | 'markdown'; + type: 'Title' | 'Body' | 'LinkText' | 'Heading'; + createdAt?: string; + updatedAt?: string; +} + +export interface Task { + id: number; + title: string; + description?: string; + assignee: string; + assigned_date?: string; + due_date: string; + start_date?: string; + close_date?: string; + status: 'Open' | 'InProgress' | 'Closed'; + created_at?: string; + updated_at?: string; + additional_info?: any; +} + +// Added missing LLM and Attachment types +export type ModelProvider = 'google' | 'openai'; + +export interface LLMConfig { + provider: ModelProvider; + model: string; + apiKey?: string; + systemInstruction?: string; +} + +export interface Attachment { + id: string; + name: string; + content: string; + isProcessing: boolean; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..824cf45 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,28 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +// FIX: Derive __dirname in ESM environment to resolve "Cannot find name '__dirname'" error +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +}); \ No newline at end of file diff --git a/wrangler.json b/wrangler.json new file mode 100644 index 0000000..a0095fe --- /dev/null +++ b/wrangler.json @@ -0,0 +1,4 @@ +{ + "name": "ai-studio-template", + "compatibility_date": "2025-10-20", +} \ No newline at end of file