diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index f00f9bc..0000000 --- a/.dockerignore +++ /dev/null @@ -1,31 +0,0 @@ - -.git -.gitignore -README.md -Dockerfile -.dockerignore -.replit -replit.nix -pyproject.toml -uv.lock -run.sh -deploy.sh -Guides/ -attached_assets/ -instance/ -logs/*.log -__pycache__/ -*.pyc -*.pyo -*.pyd -.Python -env/ -venv/ -.env -.venv -pip-log.txt -pip-delete-this-directory.txt -.DS_Store -.coverage -htmlcov/ -.pytest_cache/ diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7afe6e1..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.pyc -__pycache__/ -*.db -.env -venv/ -env/ -.DS_Store -*.log diff --git a/.replit b/.replit deleted file mode 100644 index ce558d6..0000000 --- a/.replit +++ /dev/null @@ -1,46 +0,0 @@ -entrypoint = "app.py" -modules = ["bash", "nodejs-20", "python-3.11", "web"] - -[nix] -channel = "stable-25_05" - -[unitTest] -language = "python3" - -[gitHubImport] -requiredFiles = [".replit", "replit.nix"] - -[deployment] -run = ["python3", "app.py"] -deploymentTarget = "cloudrun" - -[agent] -expertMode = true - -[[ports]] -localPort = 5000 -externalPort = 80 - -[workflows] -runButton = "Project" - -[[workflows.workflow]] -name = "Project" -mode = "parallel" -author = "agent" - -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "Flask Server" - -[[workflows.workflow]] -name = "Flask Server" -author = "agent" - -[[workflows.workflow.tasks]] -task = "shell.exec" -args = "python app.py" -waitForPort = 5000 - -[workflows.workflow.metadata] -outputType = "webview" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6dff380..0000000 --- a/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ - -# Use Python 3.11 slim image -FROM python:3.11-slim - -# Set working directory -WORKDIR /app - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - gcc \ - postgresql-client \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Copy requirements first for better caching -COPY requirements.txt . - -# Install Python dependencies -RUN pip install --upgrade pip && \ - pip install --no-cache-dir -r requirements.txt - -# Copy application code -COPY . . - -# Create logs directory -RUN mkdir -p logs - -# Set environment variables -ENV FLASK_APP=app.py -ENV FLASK_ENV=production -ENV PYTHONUNBUFFERED=1 - -# Expose port -EXPOSE 5000 - -# Create non-root user for security -RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app -USER appuser - -# Health check -HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:5000/ || exit 1 - -# Run the application -CMD ["python", "app.py"] diff --git a/Guides/AIRTABLE_INTEGRATION.md b/Guides/AIRTABLE_INTEGRATION.md deleted file mode 100644 index 67be008..0000000 --- a/Guides/AIRTABLE_INTEGRATION.md +++ /dev/null @@ -1,334 +0,0 @@ -# Airtable Integration Guide - -This guide shows you how to automatically trigger reviews when records are created or updated in Airtable. - -## Setup Steps - -### 1. Create an API Key - -1. Log in to Hack Club Vision -2. Navigate to **API Keys** in the navigation menu -3. Click **Create Key** -4. Give it a descriptive name (e.g., "Airtable Production") -5. **Copy the API key immediately** - you won't see it again! - -### 2. Configure Your Base in Hack Club Vision - -Before using the API, make sure your Airtable base is configured: - -1. Go to **Bases** in Hack Club Vision -2. Click **Add Base** -3. Enter your Base ID and Table Name -4. Let the AI detect field mappings -5. Verify the field mappings are correct - -## Airtable Automation Scripts - -### Option 1: Automation Script (Recommended) - -Create an automation in Airtable that triggers when a record is created or updated. - -**Trigger:** When record matches conditions (or when record is created) - -**Action:** Run a script - -```javascript -// ============================================ -// Hack Club Vision - Auto Review Trigger -// ============================================ - -let config = input.config(); -let recordId = config.recordId; // Provided by the trigger - -// ================== -// YOUR CONFIGURATION -// ================== -const API_URL = "https://your-app-url.com/api/v1/review"; -const API_KEY = "hcv_your_api_key_here"; // Replace with your actual API key -const BASE_ID = "appXXXXXXXXXXXXXX"; // Your Airtable base ID -const TABLE_NAME = "Your Table Name"; // Your table name - -// ================== -// SEND REVIEW REQUEST -// ================== -console.log(`🚀 Starting review for record: ${recordId}`); - -try { - let response = await fetch(API_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': API_KEY - }, - body: JSON.stringify({ - base_id: BASE_ID, - table_name: TABLE_NAME, - record_id: recordId - }) - }); - - let result = await response.json(); - - if (result.success) { - console.log('✅ Review started successfully!'); - console.log('Result:', result); - } else { - console.error('❌ Error starting review:', result.error); - } -} catch (error) { - console.error('❌ Failed to trigger review:', error); -} -``` - -### Option 2: Conditional Review (Only for Specific Status) - -Trigger reviews only when certain conditions are met: - -```javascript -// Trigger review only for "Ready for Review" status - -let config = input.config(); -let recordId = config.recordId; - -// Fetch the record to check its status -let table = base.getTable("Your Table Name"); -let record = await table.selectRecordAsync(recordId); - -// Only proceed if status is "Ready for Review" -if (record.getCellValue("Status") !== "Ready for Review") { - console.log("⏭️ Skipping review - status is not 'Ready for Review'"); - return; -} - -// Configuration -const API_URL = "https://your-app-url.com/api/v1/review"; -const API_KEY = "hcv_your_api_key_here"; -const BASE_ID = base.id; -const TABLE_NAME = table.name; - -// Trigger review -console.log(`🚀 Starting review for record: ${recordId}`); - -let response = await fetch(API_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': API_KEY - }, - body: JSON.stringify({ - base_id: BASE_ID, - table_name: TABLE_NAME, - record_id: recordId - }) -}); - -let result = await response.json(); -console.log('Review result:', result); -``` - -### Option 3: Batch Review Multiple Records - -Review multiple records at once: - -```javascript -// Review all records with a specific status - -let table = base.getTable("Your Table Name"); -let query = await table.selectRecordsAsync({ - fields: ["Status", "Code URL"] -}); - -// Configuration -const API_URL = "https://your-app-url.com/api/v1/review"; -const API_KEY = "hcv_your_api_key_here"; -const BASE_ID = base.id; -const TABLE_NAME = table.name; - -let reviewCount = 0; - -for (let record of query.records) { - // Only review records with "Pending" status - if (record.getCellValue("Status") === "Pending") { - console.log(`🚀 Starting review for: ${record.id}`); - - try { - let response = await fetch(API_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': API_KEY - }, - body: JSON.stringify({ - base_id: BASE_ID, - table_name: TABLE_NAME, - record_id: record.id - }) - }); - - let result = await response.json(); - if (result.success) { - reviewCount++; - } - } catch (error) { - console.error(`❌ Failed for ${record.id}:`, error); - } - - // Add delay to avoid rate limiting (optional) - await new Promise(resolve => setTimeout(resolve, 1000)); - } -} - -console.log(`✅ Started ${reviewCount} reviews`); -``` - -### Option 4: Button Field Integration - -Add a button field that triggers a review when clicked: - -1. Add a **Button** field to your table -2. Configure the button to **Run a script** -3. Use this script: - -```javascript -// Button click script - Review this record - -let table = base.getTable("Your Table Name"); -let record = await input.recordAsync('Select a record', table); - -if (!record) { - console.log("No record selected"); - return; -} - -// Configuration -const API_URL = "https://your-app-url.com/api/v1/review"; -const API_KEY = "hcv_your_api_key_here"; -const BASE_ID = base.id; -const TABLE_NAME = table.name; - -console.log(`🚀 Starting review for: ${record.name || record.id}`); - -let response = await fetch(API_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': API_KEY - }, - body: JSON.stringify({ - base_id: BASE_ID, - table_name: TABLE_NAME, - record_id: record.id - }) -}); - -let result = await response.json(); - -if (result.success) { - console.log('✅ Review started successfully!'); - output.markdown(`# ✅ Review Started\n\nThe AI is now reviewing this project. Check the "AI Review Notes" field in a few minutes.`); -} else { - console.error('❌ Error:', result.error); - output.markdown(`# ❌ Error\n\n${result.error}`); -} -``` - -## Finding Your Base ID - -Your Airtable Base ID can be found in the URL when viewing your base: - -``` -https://airtable.com/appXXXXXXXXXXXXXX/tblYYYYYYYYYYYYYY - ^^^^^^^^^^^^^^^^^ - This is your Base ID -``` - -## API Endpoint Reference - -### POST /api/v1/review - -Starts a new review job. - -**Headers:** -``` -X-API-Key: your_api_key_here -Content-Type: application/json -``` - -**Body:** -```json -{ - "base_id": "appXXXXXXXXXXXXXX", - "table_name": "Your Table Name", - "record_id": "recXXXXXXXXXXXXXX" -} -``` - -**Response (Success):** -```json -{ - "success": true, - "message": "Review started", - "base_id": "appXXXXXXXXXXXXXX", - "table_name": "Your Table Name", - "record_id": "recXXXXXXXXXXXXXX" -} -``` - -**Response (Error):** -```json -{ - "error": "Error message here" -} -``` - -### GET /api/v1/job/{job_id} - -Get the status of a review job. - -**Headers:** -``` -X-API-Key: your_api_key_here -``` - -**Response:** -```json -{ - "id": 123, - "status": "completed", - "result": { - "status": "Approved", - "review_notes": "Great project!", - "user_feedback": "I loved your responsive design..." - } -} -``` - -## Troubleshooting - -### "API key required" error -- Make sure you're including the `X-API-Key` header -- Check that your API key is correct - -### "Base not configured" error -- Add your base in the Hack Club Vision dashboard first -- Verify the Base ID and Table Name match exactly - -### "Invalid or inactive API key" error -- Your API key may have been disabled -- Check the API Keys page and make sure it's active - -### Reviews not appearing -- Check that field mappings are correct in your base configuration -- Look at the Jobs page to see if reviews are running - -## Security Best Practices - -1. **Keep your API key secret** - Don't share it publicly -2. **Use different keys** for different environments (testing vs. production) -3. **Disable unused keys** - Turn off keys you're not using -4. **Rotate keys periodically** - Create new keys and delete old ones -5. **Monitor usage** - Check the "Last Used" timestamp on the API Keys page - -## Need Help? - -If you run into issues, check the **Jobs** page in Hack Club Vision to see detailed logs and error messages for each review. diff --git a/Guides/API_DOCUMENTATION.md b/Guides/API_DOCUMENTATION.md deleted file mode 100644 index 9547ad1..0000000 --- a/Guides/API_DOCUMENTATION.md +++ /dev/null @@ -1,663 +0,0 @@ -# API Documentation - Hack Club Vision v2.0 - -## Base URL -``` -http://your-domain.com -``` - -All API endpoints require authentication via Flask session cookies (set after login). - ---- - -## Authentication - -### POST /login -Login to the system - -**Request:** -```json -{ - "username": "string", - "password": "string" -} -``` - -**Response:** -```json -{ - "success": true -} -``` - -**Errors:** -- 401: Invalid credentials - ---- - -### POST /register -Register a new account - -**Request:** -```json -{ - "username": "string", - "password": "string" -} -``` - -**Response:** -```json -{ - "success": true -} -``` - ---- - -### GET /logout -Logout current user - -**Response:** Redirect to login page - ---- - -## Airtable Base Management - -### POST /api/add-base -Add and scan a new Airtable base - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects" -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Base added successfully", - "mappings": { - "code_url": "GitHub URL", - "playable_url": "Demo Link", - "hackatime_hours": "Hours Worked", - "ai_review_notes": "Internal Notes", - "ai_user_feedback": "User Feedback" - } -} -``` - -**Errors:** -- 400: Error detecting fields -- 400: Base already exists - ---- - -### DELETE /api/delete-base/ -Delete an Airtable base configuration - -**Parameters:** -- `base_id` (integer): Database ID of the base - -**Response:** -```json -{ - "success": true, - "message": "Base deleted successfully" -} -``` - -**Errors:** -- 404: Base not found - ---- - -### POST /api/edit-field-mappings -Edit field mappings for an existing base - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "mappings": { - "code_url": "GitHub Repository", - "playable_url": "Live Demo", - "hackatime_hours": "Development Hours", - "ai_review_notes": "Review Notes", - "ai_user_feedback": "Feedback" - } -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Field mappings updated successfully" -} -``` - -**Errors:** -- 404: Base not found -- 400: Missing required field - ---- - -### POST /api/create-field -Update field mapping after creating a field in Airtable - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "field_name": "AI Review Notes", - "field_key": "ai_review_notes", - "field_type": "multilineText" -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Field mapping updated" -} -``` - ---- - -## Record Search - -### POST /api/search-records -Search for records in an Airtable table - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "search_query": "optional search term" -} -``` - -**Response:** -```json -{ - "records": [ - { - "id": "rec123abc", - "fields": { - "Name": "My Project", - "GitHub URL": "https://github.com/user/repo", - "Hours": 15 - } - } - ] -} -``` - -**Limits:** -- Returns first 100 matching records -- Shows only first 20 if search query provided - ---- - -## Review Jobs - -### POST /api/start-review -Start a review job for a single record - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "record_id": "rec123abc" -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Review started" -} -``` - -**Errors:** -- 400: Base not configured - -**Job Processing:** -- Runs in background thread -- Updates database in real-time -- Can be monitored via /api/jobs endpoint - ---- - -### POST /api/bulk-review -Start review jobs for multiple records - -**Request:** -```json -{ - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "record_ids": [ - "rec123abc", - "rec456def", - "rec789ghi" - ] -} -``` - -**Response:** -```json -{ - "success": true, - "message": "Started 3 review jobs", - "count": 3 -} -``` - -**Limits:** -- Maximum 100 records per bulk operation - -**Errors:** -- 400: Too many records (>100) -- 400: Base not configured - ---- - -### GET /api/jobs -Get all running and completed jobs for current user - -**Response:** -```json -{ - "running": [ - { - "id": 123, - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "record_id": "rec123abc", - "status": "running", - "current_step": "Step 2: Testing project functionality...", - "result": null, - "details": { - "steps": [ - { - "name": "GitHub URL Validation", - "status": "Passed", - "result": { - "code_url": "https://github.com/user/repo", - "is_github": true - } - }, - { - "name": "Check for Duplicate Submission", - "status": "Already submitted: false", - "result": { - "is_duplicate": false, - "code_url": "https://github.com/user/repo", - "playable_url": "https://demo.com" - } - } - ] - }, - "created_at": "2025-10-02T17:30:00.000Z", - "completed_at": null - } - ], - "history": [ - { - "id": 122, - "base_id": "app3A5kJwYqxMLOgh", - "table_name": "Projects", - "record_id": "rec456def", - "status": "completed", - "current_step": "Complete: Approved", - "result": { - "status": "Approved", - "confidence_score": 9, - "review_notes": "High quality original project. Quality score: 8/10, Originality: 7/10. Consistent commit pattern over 5 days with 23 commits. Code volume appropriate for 15 hours claimed.", - "user_feedback": "" - }, - "details": { - "steps": [ - { - "name": "GitHub URL Validation", - "status": "Passed", - "result": {"is_github": true} - }, - { - "name": "Check for Duplicate Submission", - "status": "Already submitted: false", - "result": {"is_duplicate": false} - }, - { - "name": "Test Project Functionality", - "status": "Working: True, Legitimate: True", - "result": { - "is_working": true, - "is_legitimate": true, - "originality_score": 7, - "quality_score": 8, - "features": ["Interactive game", "Score tracking", "Multiple levels"], - "red_flags": [], - "assessment": "Well-developed game with original mechanics", - "technical_details": { - "forms": 1, - "buttons": 8, - "scripts": 12, - "frameworks": ["React"] - } - } - }, - { - "name": "Analyze GitHub Commits", - "status": "Pattern: consistent, Matches hours: true", - "result": { - "commits_match_hours": true, - "commit_pattern": "consistent", - "commit_quality_score": 7, - "code_volume_appropriate": true, - "estimated_actual_hours": 14, - "red_flags": [], - "assessment": "Regular development over 5 days", - "metadata": { - "total_commits": 23, - "time_span_days": 5, - "total_additions": 847, - "total_deletions": 123 - } - } - }, - { - "name": "AI Final Decision", - "status": "Decision: Approved", - "result": { - "status": "Approved", - "confidence_score": 9, - "review_notes": "All criteria met for approval", - "user_feedback": "" - } - } - ] - }, - "created_at": "2025-10-02T17:15:00.000Z", - "completed_at": "2025-10-02T17:16:30.000Z" - } - ] -} -``` - -**Limits:** -- Returns last 100 completed jobs -- All running jobs - ---- - -### POST /api/cancel-job/ -Cancel a running job - -**Parameters:** -- `job_id` (integer): Database ID of the job - -**Response:** -```json -{ - "success": true, - "message": "Job cancelled" -} -``` - -**Errors:** -- 404: Job not found -- 400: Job is not running - -**Effect:** -- Job status changed to "cancelled" -- Completion timestamp set -- Job moves to history - ---- - -### DELETE /api/delete-job/ -Delete a completed/failed/cancelled job - -**Parameters:** -- `job_id` (integer): Database ID of the job - -**Response:** -```json -{ - "success": true, - "message": "Job deleted" -} -``` - -**Errors:** -- 404: Job not found -- 400: Cannot delete running job (must cancel first) - -**Effect:** -- Permanent deletion from database -- Cannot be recovered - ---- - -## Page Routes - -### GET / -Redirect to dashboard (if logged in) or login page - ---- - -### GET /login -Display login page - ---- - -### GET /register -Display registration page - ---- - -### GET /dashboard -Display main dashboard with Airtable bases - -**Requires:** Authentication - -**Features:** -- Add new bases -- View connected bases -- See field mappings -- Start single reviews -- Delete bases - ---- - -### GET /jobs -Display jobs monitoring page - -**Requires:** Authentication - -**Features:** -- View running jobs -- View job history (last 100) -- View detailed job information -- Manual refresh button - ---- - -## Error Responses - -All endpoints may return these standard error responses: - -### 400 Bad Request -```json -{ - "error": "Detailed error message" -} -``` - -### 401 Unauthorized -```json -{ - "error": "Authentication required" -} -``` - -### 404 Not Found -```json -{ - "error": "Resource not found" -} -``` - -### 500 Internal Server Error -```json -{ - "error": "Internal server error", - "running": [], - "history": [] -} -``` - ---- - -## Rate Limiting - -**Current Limits:** -- No rate limiting implemented -- Consider adding for production: - - 100 requests/minute per user - - 10 bulk reviews/hour per user - - 1000 API calls/day per user - ---- - -## Webhooks (Future Feature) - -Not yet implemented. Potential future endpoints: - -- `POST /api/webhooks/airtable` - Receive Airtable webhook events -- `POST /api/webhooks/github` - Receive GitHub webhook events -- `GET /api/webhooks/configure` - Configure webhook settings - ---- - -## Database Schema - -### User -```python -{ - "id": Integer (PK), - "username": String(80, unique), - "password": String(120) -} -``` - -### AirtableBase -```python -{ - "id": Integer (PK), - "user_id": Integer (FK), - "base_id": String(120), - "table_name": String(120), - "field_mappings": Text (JSON string) -} -``` - -### ReviewJob -```python -{ - "id": Integer (PK), - "user_id": Integer (FK), - "base_id": String(120), - "table_name": String(120), - "record_id": String(120), - "status": String(50), # "running", "completed", "failed", "cancelled" - "current_step": String(200), - "result": Text (JSON string), - "details": Text (JSON string), - "created_at": DateTime, - "completed_at": DateTime -} -``` - ---- - -## Example Usage - -### Complete Review Workflow - -```javascript -// 1. Login -await fetch('/login', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - username: 'reviewer', - password: 'secure_password' - }) -}); - -// 2. Add Airtable base -const addResponse = await fetch('/api/add-base', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - base_id: 'app3A5kJwYqxMLOgh', - table_name: 'Submissions' - }) -}); - -// 3. Search for records -const searchResponse = await fetch('/api/search-records', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - base_id: 'app3A5kJwYqxMLOgh', - table_name: 'Submissions', - search_query: 'pending' - }) -}); -const {records} = await searchResponse.json(); - -// 4. Start bulk review -const recordIds = records.map(r => r.id); -await fetch('/api/bulk-review', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - base_id: 'app3A5kJwYqxMLOgh', - table_name: 'Submissions', - record_ids: recordIds - }) -}); - -// 5. Monitor jobs -const interval = setInterval(async () => { - const jobsResponse = await fetch('/api/jobs'); - const {running, history} = await jobsResponse.json(); - - console.log(`Running: ${running.length}, Completed: ${history.length}`); - - if (running.length === 0) { - clearInterval(interval); - console.log('All reviews complete!'); - } -}, 2000); -``` - ---- - -**Version:** 2.0 -**Last Updated:** 2025-10-02 -**Authentication:** Session-based (Flask-Login) -**Content-Type:** application/json diff --git a/Guides/CHANGELOG.md b/Guides/CHANGELOG.md deleted file mode 100644 index 2edbc7e..0000000 --- a/Guides/CHANGELOG.md +++ /dev/null @@ -1,318 +0,0 @@ -# Changelog - Hack Club Vision - -## Version 2.0 - Database Persistence & Enhanced Review System (Latest) - -### 🎯 Major Changes - -**Database-Persisted Jobs** -- Jobs now stored in SQLite database instead of in-memory -- All job history persists across server restarts -- Added `user_id`, `details`, and `completed_at` fields to ReviewJob model -- Users can now see all their historical job data -- Last 50 jobs displayed per user -- Location: `app.py:82-108` - -**GitHub URL Validation** -- Code URL must be a GitHub link or job is automatically flagged -- Validation happens before any processing begins -- Saves API calls and processing time for invalid submissions -- Clear user feedback when non-GitHub URL detected -- Location: `app.py:474-508` - -### 🔍 Enhanced Review Process - -**Significantly Improved Duplicate Detection** -- URL normalization (removes http/https, www, trailing slashes) -- Case-insensitive matching -- GitHub repository matching (owner/repo comparison) -- Checks against entire unified database -- Location: `app.py:143-187` - -**Vastly Enhanced Project Testing** -- Analyzes 8000 chars of content (up from 5000) -- Detects interactive elements (forms, buttons, inputs, scripts) -- Framework detection (React, Vue, Angular, Bootstrap, Tailwind) -- New scoring system: originality_score (1-10) and quality_score (1-10) -- Identifies red flags (copied templates, tutorials, placeholders) -- Strict criteria for legitimate vs copied projects -- Technical metadata included in results -- Location: `app.py:189-264` - -**Comprehensive Commit Analysis** -- Analyzes up to 50 commits (up from 20) -- Fetches detailed stats for each commit (additions/deletions) -- Calculates time span of development -- Tracks multiple authors -- New metrics: commit_quality_score, code_volume_appropriate, estimated_actual_hours -- Detects suspicious patterns (bulk commits, generic messages, timing anomalies) -- Strict evaluation against claimed hours -- Identifies red flags automatically -- Location: `app.py:266-386` - -**Stricter Final Decision Logic** -- Explicit automatic rejection criteria (10+ rules) -- Clear flagging criteria for manual review (8+ rules) -- Strict approval requirements (10 must-pass criteria) -- Confidence scoring (1-10) -- Detailed reasoning citing specific scores -- Lower approval threshold to ensure quality -- When in doubt, flags for human review -- Location: `app.py:388-442` - -### 📊 Review Criteria - -**Automatic Rejection:** -- Duplicate submission -- Project not working -- Project not legitimate -- Quality score < 4 -- Originality score < 3 -- No GitHub repo/commits - -**Flag for Manual Review:** -- Hours mismatch > 5 hours -- Suspicious/bulk commit patterns -- Low commit quality (< 5/10) -- Marginal quality (4-6/10) -- Red flags detected -- Questionable originality (3-5/10) - -**Approval Requirements (ALL must pass):** -- Not duplicate -- Project working and legitimate -- Quality score ≥ 7/10 -- Originality score ≥ 6/10 -- Commits match hours -- Consistent commit pattern -- Commit quality ≥ 6/10 -- Appropriate code volume -- No major red flags - -### 🔧 Technical Improvements - -**Database Schema Enhancement** -- Added `ReviewJob` model with full tracking -- Relationships: User → ReviewJob (one-to-many) -- JSON serialization with `to_dict()` method -- Timestamps for created_at and completed_at - -**API Updates** -- `/api/jobs` now queries database instead of in-memory storage -- Supports filtering by user and status -- Returns last 50 jobs per user -- Proper ordering by creation date - -**Error Handling** -- All review steps wrapped in try-catch -- Detailed error tracking in job details -- Graceful fallbacks for API failures -- Network timeouts increased (15s) - -### 🚀 Performance - -- Jobs persist across restarts (no data loss) -- More thorough analysis with detailed metrics -- Better GitHub API utilization (50 commits + stats) -- Reduced false positives with stricter criteria -- Early exit on GitHub validation failure - -### 📝 Breaking Changes - -**Database Migration Required:** -```bash -python3 -c "from app import app, db; app.app_context().push(); db.create_all()" -``` - -**Function Signature Change:** -- `run_review_job()` now requires `user_id` as first parameter -- All existing background threads must be updated - ---- - -## Version 1.2 - Jobs Page Enhancement - -### ✨ New Features - -**Detailed Job View Modal** -- Added "View Full Details" button for completed jobs -- Modal displays all 4 review steps with AI thinking -- Shows AI results for each step in formatted JSON -- Displays any errors encountered during processing -- Step-by-step breakdown: Duplicate Check, Project Test, Commit Analysis, Final Decision -- Location: `templates/jobs.html:55-313` - -**Manual Refresh Control** -- Removed auto-update polling (was 2 seconds) -- Added manual "Refresh" button with spin animation -- Users now control when to reload job status -- Improved performance and reduced API calls -- Location: `templates/jobs.html:13-17, 185-195` - -### 🔧 Backend Improvements - -**Enhanced Job Processing** -- `run_review_job()` now stores detailed step information -- Added `details` field with `steps` array to job data -- Each step captures: name, status, result, and errors -- Try-catch blocks around each step for error isolation -- Comprehensive error tracking throughout pipeline -- Location: `app.py:244-368` - -### 🎨 UI Improvements - -**Job Details Modal** -- Beautiful modal with scrollable content -- Color-coded sections (blue for steps, green/red for results) -- Numbered step indicators -- Expandable JSON result display -- Error messages highlighted in red -- Professional layout with Font Awesome icons - ---- - -## Version 1.1 - Enhanced Animations & Bug Fixes - -### 🐛 Bug Fixes - -**Fixed ShuttleAI API Response Validation Error** -- Created `call_ai()` wrapper function to handle response validation issues -- All AI calls now use centralized error handling -- Gracefully handles Pydantic validation errors from ShuttleAI SDK -- Location: `app.py:36-56` - -**Fixed Jinja2 Template Error** -- Added custom `fromjson` filter for Jinja2 templates -- Allows dashboard to properly parse JSON field mappings -- Location: `app.py:32-34` - -### ✨ New Features - -**Animated Loading Modal** -- Beautiful loading modal with spinning animation and AI brain icon -- Shows during table scanning and review job initialization -- Smooth fade-in/fade-out transitions -- Dynamic title and message updates -- Location: `templates/dashboard.html:134-159` - -**Enhanced Form Interactions** -- Login form now shows loading spinner during authentication -- Success state with green checkmark animation -- Error messages pulse to draw attention -- Register form has similar enhanced feedback -- Buttons disabled during processing to prevent double-submission - -**Jobs Page Animations** -- Running jobs have animated border and "Live" badge -- Status icons include pulse animations -- Smooth fade-in when jobs appear -- Bouncing arrow indicator for current step -- Real-time updates with visual feedback - -**Dashboard Improvements** -- Modal scale animations when opening/closing review dialog -- Success notification toast when review starts -- Smooth transitions on all interactive elements -- Result messages animate with pulse effect - -### 🎨 UI/UX Improvements - -**Modal Animations** -- Review modal scales up smoothly on open -- Loading modal fades in with backdrop -- Progress dots bounce in sequence -- All modals have proper z-index layering - -**Button States** -- Loading states show spinner icons -- Success states change color to green -- Hover effects with smooth transitions -- Disabled states during processing - -**Status Indicators** -- Color-coded badges (Approved: green, Rejected: red, Flagged: yellow) -- Animated spinner for running jobs -- Pulse effect on live status indicators -- Professional icon set throughout - -### 🔧 Technical Improvements - -**Error Handling** -- Comprehensive try-catch blocks in all async functions -- User-friendly error messages -- Console logging for debugging -- Graceful fallbacks for API failures - -**Code Organization** -- Centralized AI calling logic -- Reusable modal show/hide functions -- Consistent animation naming -- Clean separation of concerns - -### 📝 CSS Additions - -Added custom animations: -- `@keyframes fadeIn` - Smooth opacity transition -- `@keyframes slideUp` - Slide from bottom effect -- `@keyframes slideIn` - Slide from left effect -- `.modal-show` - Modal appearance animation -- `.modal-content-show` - Content slide animation - -### 🚀 Performance - -- Animations are CSS-based for smooth 60fps performance -- Minimal JavaScript for state management -- Efficient DOM updates -- Non-blocking async operations - ---- - -## Version 1.0 - Initial Release - -### Core Features -- User authentication system -- Airtable multi-base integration -- AI-powered field detection -- 4-step automated review pipeline -- Jobs monitoring dashboard -- Tailwind CSS UI with Font Awesome icons - -### Review Pipeline -- Step 1: Duplicate detection in unified database -- Step 2: AI project functionality testing -- Step 3: GitHub commit history analysis -- Step 4: Automated approval/rejection/flagging - -### Technology Stack -- Flask + SQLAlchemy -- ShuttleAI (OpenAI GPT-5) -- Airtable API -- Tailwind CSS -- Font Awesome - ---- - -## Upcoming Features (Roadmap) - -- [ ] Email notifications for review completion -- [ ] Bulk review processing -- [ ] Advanced analytics dashboard -- [ ] Custom review criteria configuration -- [ ] Webhook integration -- [ ] Multi-language support -- [ ] Export reports as PDF/CSV -- [ ] Dark mode theme -- [ ] Mobile-responsive optimizations -- [ ] Real-time WebSocket updates (replace polling) - ---- - -## Migration Notes - -### From 1.0 to 1.1 - -No database migrations required. Simply: -1. Pull latest code -2. Restart Flask application -3. Clear browser cache for best experience - -All changes are backward compatible. diff --git a/Guides/REVIEW_CRITERIA.md b/Guides/REVIEW_CRITERIA.md deleted file mode 100644 index 23fe97f..0000000 --- a/Guides/REVIEW_CRITERIA.md +++ /dev/null @@ -1,427 +0,0 @@ -# Review Criteria - Hack Club Vision v2.0 - -## Overview - -This document outlines the comprehensive review criteria used by Hack Club Vision's AI-powered review system. - ---- - -## Pre-Review Validation - -### GitHub URL Check - -**Requirement:** Code URL MUST be a GitHub repository link. - -**Valid Examples:** -- `https://github.com/username/repository` -- `http://github.com/username/repository` -- `github.com/username/repository` - -**Invalid Examples:** -- `https://gitlab.com/username/repository` -- `https://bitbucket.org/username/repository` -- `https://example.com/myproject` -- Empty or missing URL - -**Action if Invalid:** Automatically flagged with message to user. - ---- - -## Step 1: Duplicate Detection - -### Purpose -Prevent the same project from being submitted multiple times across different tables/bases. - -### Checks Performed - -1. **URL Normalization:** - - Remove `http://`, `https://`, `www.` - - Remove trailing slashes - - Convert to lowercase - -2. **Exact URL Matching:** - - Compare normalized Code URLs - - Compare normalized Playable URLs - -3. **GitHub Repository Matching:** - - Extract owner/repo from GitHub URLs - - Match `github.com/owner/repo` pattern - - Catches renamed branches or different views of same repo - -### Database Checked -`Approved Projects` table in unified Airtable base (app3A5kJwYqxMLOgh) - -### Result -- **True:** Project already submitted → Automatic Rejection -- **False:** Unique project → Continue review - ---- - -## Step 2: Project Testing - -### Purpose -Verify the project is working, legitimate, and shows original effort. - -### Analysis Performed - -1. **Content Analysis:** - - Fetches up to 8000 characters from playable URL - - Extracts all text content - - Identifies interactive links - -2. **Technical Detection:** - - Counts forms, buttons, inputs, scripts - - Detects frameworks (React, Vue, Angular, Bootstrap, Tailwind) - - Analyzes HTML structure - -3. **AI Evaluation:** - - Is the project functional? - - Is it legitimate or just a placeholder? - - Signs of originality vs copied code - - Quality of implementation - -### Scoring System - -**Originality Score (1-10):** -- 1-2: Direct copy of tutorial -- 3-5: Template with minimal changes -- 6-7: Template with significant customization -- 8-9: Mostly original with some references -- 10: Completely original implementation - -**Quality Score (1-10):** -- 1-2: Broken or non-functional -- 3-4: Basic functionality, poor quality -- 5-6: Working but minimal effort -- 7-8: Good quality, complete features -- 9-10: Excellent quality, polished - -### Red Flags Detected - -- "Hello World" or tutorial starter code -- Generic placeholder content -- Empty pages or error messages -- No interactive elements -- Copied template without changes -- Lorem ipsum text -- Default framework examples - -### Rejection Criteria - -**Automatic Rejection if:** -- `is_working = false` -- `is_legitimate = false` -- `quality_score < 4` -- `originality_score < 3` - -**Flagged if:** -- `quality_score` between 4-6 -- `originality_score` between 3-5 -- Any red flags present - -**Approval Threshold:** -- `quality_score >= 7` -- `originality_score >= 6` -- `is_working = true` -- `is_legitimate = true` - ---- - -## Step 3: Commit Analysis - -### Purpose -Verify claimed hours match actual development effort and quality. - -### Data Collected - -**Commit Information:** -- Up to 50 most recent commits -- Commit messages -- Author names -- Timestamps -- Lines added/deleted per commit - -**Metadata Calculated:** -- Total commits -- Development time span (days) -- Total additions and deletions -- Number of unique authors -- Commit frequency pattern - -### Analysis Criteria - -**Commit Pattern Types:** - -1. **Consistent** (Good): - - Regular commits over multiple days - - Gradual progress visible - - Descriptive commit messages - - Reasonable code changes per commit - -2. **Bulk** (Suspicious): - - Most commits in single day - - Large code dumps - - "Initial commit" with thousands of lines - - Suggests copied or rushed work - -3. **Sparse** (Questionable): - - Very few commits for claimed hours - - Long gaps between commits - - May indicate inflated hours - -4. **Suspicious** (Red Flag): - - All commits at unusual times (3am) - - Commits at exact intervals (automated?) - - Generic messages only ("update", "fix") - - Multiple huge commits - -### Scoring System - -**Commit Quality Score (1-10):** -- 1-2: Generic messages only ("update", "fix") -- 3-4: Minimal description -- 5-6: Basic but understandable -- 7-8: Descriptive and clear -- 9-10: Excellent detail and context - -**Code Volume Analysis:** -- Expected: 20-50 lines per hour for quality code -- Too little: Possibly inflated hours -- Too much in short time: Possibly copied - -**Estimated Actual Hours:** -AI estimates realistic hours based on: -- Commit frequency and timing -- Code volume -- Complexity of changes -- Development pattern - -### Red Flags Detected - -- All commits on single day for 10+ hours claimed -- Only 1-2 commits for 10+ hours claimed -- Huge initial commit (thousands of lines at once) -- All commit messages are generic -- Commits spaced at exact intervals -- Code volume doesn't match time span -- Multiple authors (unless clearly collaborative) - -### Rejection/Flag Criteria - -**Flagged if:** -- `commits_match_hours = false` AND difference > 5 hours -- `commit_pattern = "suspicious"` or `"bulk"` -- `commit_quality_score < 5` -- `estimated_actual_hours` differs significantly from claimed -- Any red flags detected - -**Approval Threshold:** -- `commits_match_hours = true` OR reasonable explanation -- `commit_pattern = "consistent"` -- `commit_quality_score >= 6` -- `code_volume_appropriate = true` - ---- - -## Step 4: Final Decision - -### Purpose -Synthesize all data into a final Approved/Rejected/Flagged decision. - -### Decision Logic - -#### Automatic Rejection - -**Any ONE of these causes immediate rejection:** - -1. `already_submitted = true` (duplicate) -2. `project_test.is_working = false` -3. `project_test.is_legitimate = false` -4. `project_test.quality_score < 4` -5. `project_test.originality_score < 3` -6. No GitHub repo found -7. No commits found in repository - -**User Feedback:** Specific reason for rejection - -#### Flag for Manual Review - -**Any ONE of these triggers human review:** - -1. Hours mismatch > 5 hours -2. Commit pattern is "suspicious" or "bulk" -3. Commit quality score < 5 -4. Project quality score between 4-6 (marginal) -5. Project originality score between 3-5 (questionable) -6. Red flags in project test -7. Red flags in commit analysis -8. Estimated actual hours differs significantly - -**User Feedback:** Explanation of concerns for reviewer - -#### Automatic Approval - -**ALL of these must be TRUE:** - -1. NOT already submitted -2. `project_test.is_working = true` -3. `project_test.is_legitimate = true` -4. `project_test.quality_score >= 7` -5. `project_test.originality_score >= 6` -6. `commit_review.commits_match_hours = true` -7. `commit_review.commit_pattern = "consistent"` -8. `commit_review.commit_quality_score >= 6` -9. `commit_review.code_volume_appropriate = true` -10. No major red flags - -**User Feedback:** Empty (approval is silent) - -### Confidence Score - -AI also provides a confidence score (1-10) indicating how certain it is of the decision: - -- 1-3: Low confidence, should be reviewed -- 4-6: Moderate confidence -- 7-8: High confidence -- 9-10: Very high confidence - -Low confidence scores may automatically trigger flagging regardless of decision. - -### Review Notes - -Detailed internal justification including: -- Specific scores that influenced decision -- Which criteria were met/failed -- Red flags identified -- Reasoning for final decision - -These are stored in Airtable's AI Review Notes field. - ---- - -## Approval Statistics (Expected) - -Based on strict criteria: - -- **~30-40%** Auto-Approved (high quality, clear originality) -- **~40-50%** Flagged for Review (marginal cases, red flags) -- **~10-20%** Auto-Rejected (clear violations, low quality) - -This ensures high quality while minimizing false positives. - ---- - -## Examples - -### Example 1: Clear Approval - -**Submission:** -- Code: `github.com/user/game` -- Hours: 15 -- Playable: `user.github.io/game` - -**Results:** -- Duplicate: False -- Quality Score: 8/10 -- Originality Score: 7/10 -- Working: True, Legitimate: True -- Commits: 23 over 5 days -- Commit Pattern: Consistent -- Commit Quality: 7/10 -- Estimated Hours: 14 - -**Decision:** ✅ APPROVED - ---- - -### Example 2: Clear Rejection - -**Submission:** -- Code: `github.com/user/template-copy` -- Hours: 10 -- Playable: `user.github.io/template` - -**Results:** -- Duplicate: False -- Quality Score: 3/10 -- Originality Score: 2/10 -- Working: True, Legitimate: False -- Red Flags: ["Tutorial copy", "No customization"] - -**Decision:** ❌ REJECTED -**Reason:** "Project appears to be an unchanged copy of a tutorial template with minimal effort." - ---- - -### Example 3: Flagged for Review - -**Submission:** -- Code: `github.com/user/project` -- Hours: 20 -- Playable: `user.github.io/project` - -**Results:** -- Duplicate: False -- Quality Score: 6/10 (marginal) -- Originality Score: 5/10 (questionable) -- Working: True, Legitimate: True -- Commits: 3 over 1 day -- Commit Pattern: Bulk -- Estimated Hours: 8 - -**Decision:** ⚠️ FLAGGED -**Reason:** "Project quality is marginal. All commits made in single day despite 20 hours claimed. Estimated actual effort: 8 hours. Requires human review." - ---- - -### Example 4: Non-GitHub URL - -**Submission:** -- Code: `gitlab.com/user/project` -- Hours: 10 -- Playable: `example.com` - -**Decision:** ⚠️ FLAGGED (immediate) -**Reason:** "Code URL must be a GitHub repository link." - ---- - -## Calibration and Adjustment - -Review criteria can be adjusted by modifying the `finalize_review()` function in `app.py`: - -### Making Stricter: -- Increase score thresholds -- Add more rejection criteria -- Reduce hour mismatch tolerance - -### Making Lenient: -- Decrease score thresholds -- Move rejection criteria to flagging -- Increase hour mismatch tolerance - -**Recommended:** Monitor flagged items for 1-2 weeks before adjusting to avoid over-correction. - ---- - -## Maintenance - -### Regular Reviews - -1. **Weekly:** Review flagged submissions for false positives -2. **Monthly:** Analyze approval/rejection rates -3. **Quarterly:** Update red flag detection based on new patterns - -### Quality Metrics - -Track over time: -- Approval rate -- Flag rate -- Rejection rate -- False positive rate (approved but should be rejected) -- False negative rate (rejected but should be approved) - ---- - -**Last Updated:** 2025-10-02 -**Version:** 2.0 diff --git a/README.md b/README.md deleted file mode 100644 index 983c1b4..0000000 --- a/README.md +++ /dev/null @@ -1,229 +0,0 @@ -# Vision - By Hack Club - -**Version 2.0** - Production-ready automated project review system with PostgreSQL, bulk operations, and comprehensive AI analysis. - -## 🚀 Key Features - -### Core Functionality -- **Account System**: Secure user authentication with session management -- **Multi-Base Support**: Connect unlimited Airtable bases and tables -- **AI Field Detection**: Automatically maps required fields in your tables -- **Field Mapping Editor**: Edit field mappings without re-adding bases - -### Advanced Review System -- **4-Step Automated Pipeline**: - - Step 0: GitHub URL validation (auto-flags non-GitHub links) - - Step 1: Enhanced duplicate detection with URL normalization - - Step 2: Comprehensive project testing (quality + originality scores) - - Step 3: Deep commit analysis (50 commits with detailed stats) - - Step 4: Strict AI decision with confidence scoring - -### Production Features -- **PostgreSQL Support**: Production-grade database with connection pooling -- **Bulk Operations**: Review up to 100 records simultaneously -- **Job Management**: Cancel running jobs, delete completed jobs -- **Production Logging**: Rotating logs with full error tracking -- **Real-time Monitoring**: Track all jobs and their detailed progress - -### Review Scoring -- **Quality Score** (1-10): Overall project quality -- **Originality Score** (1-10): Detects templates and tutorials -- **Commit Quality Score** (1-10): Commit message quality -- **Confidence Score** (1-10): AI's confidence in decision - -## 📊 Review Criteria - -**Auto-Approval Requirements (ALL must pass):** -- Quality ≥ 7/10 -- Originality ≥ 6/10 -- Working and legitimate project -- Consistent commit pattern -- Code volume matches hours -- No major red flags - -**Auto-Rejection Triggers:** -- Duplicate submission -- Non-GitHub URL -- Project not working -- Quality < 4/10 -- Originality < 3/10 - -**Flagged for Manual Review:** -- Marginal quality/originality scores -- Hours mismatch > 5 hours -- Suspicious commit patterns -- Red flags detected - -## 🛠️ Setup - -### Prerequisites - -- Python 3.8+ -- PostgreSQL database (or SQLite for local dev) -- Airtable Personal Access Token -- ShuttleAI API Key - -### Quick Start (Production) - -```bash -# Set environment variables -export DATABASE_URL="postgresql://user:pass@host:5432/dbname" -export AIRTABLE_PAT="your_airtable_personal_access_token" -export SHUTTLE_AI_KEY="your_shuttleai_api_key" - -# Run deployment script -./deploy.sh - -# Start application -python3 app.py -``` - -### Manual Setup - -```bash -# Install dependencies -pip install -r requirements.txt - -# Create database tables -python3 -c "from app import app, db; app.app_context().push(); db.create_all()" - -# Start application -python3 app.py -``` - -### Installation - -1. Navigate to the project directory: -```bash -cd hack-club-vision -``` - -2. Install dependencies: -```bash -pip install -r requirements.txt -``` - -3. Run the application: -```bash -python app.py -``` - -4. Open your browser and navigate to `http://localhost:5000` - -## Usage - -### 1. Create an Account - -- Register a new account on the login page -- Sign in with your credentials - -### 2. Add an Airtable Base - -- Go to the Dashboard -- Enter your Base ID (e.g., `app3A5kJwYqxMLOgh`) -- Enter the Table Name (e.g., `Projects`) -- Click "Scan & Add Base" - -The system will: -- Scan the table structure -- Use AI to detect which fields correspond to: - - Code URL (GitHub repository) - - Playable URL (live demo) - - Hackatime Hours - - AI Review Notes - - AI User Feedback -- Create missing fields if needed - -### 3. Start a Review - -- From the Dashboard, click "Start Review" on a connected base -- Enter the Record ID you want to review -- The system will automatically: - 1. Check if the project was already submitted - 2. Test the project's functionality - 3. Analyze GitHub commits - 4. Generate a review decision (Approved/Rejected/Flagged) - 5. Update the Airtable record with review notes and user feedback - -### 4. Monitor Jobs - -- Navigate to the Jobs tab -- View running jobs with real-time progress updates -- Review completed jobs with full details - -## Unified Database Configuration - -The system checks submissions against a unified database: - -- **Base ID**: `app3A5kJwYqxMLOgh` -- **Table**: `Approved Projects` -- **Fields**: Email, Playable URL, Code URL - -## AI Model - -The application uses `openai/gpt-5` via ShuttleAI for: -- Field detection and mapping -- Project functionality testing -- Commit analysis -- Final review decisions - -## Architecture - -- **Backend**: Flask (Python) -- **Database**: SQLite with SQLAlchemy -- **Frontend**: Tailwind CSS + Font Awesome -- **APIs**: Airtable, ShuttleAI, GitHub -- **Job Processing**: Multi-threaded background jobs - -## Review Logic - -### Step 1: Duplicate Check -Searches the unified database for matching Code URL or Playable URL - -### Step 2: Project Testing -- Fetches the project website -- Analyzes content and features -- Determines if the project is legitimate and functional - -### Step 3: Commit Analysis -- Fetches GitHub commit history -- Compares commits to claimed hours -- Identifies commit patterns (consistent vs. bulk) - -### Step 4: Finalization -AI determines the final status: -- **Approved**: Project passes all checks -- **Rejected**: Already submitted or project is non-functional -- **Flagged**: Suspicious patterns requiring manual review - -## Database Schema - -### User -- id (Primary Key) -- username (Unique) -- password -- bases (Relationship) - -### AirtableBase -- id (Primary Key) -- user_id (Foreign Key) -- base_id -- table_name -- field_mappings (JSON) - -### ReviewJob -- id (Primary Key) -- base_id -- table_name -- record_id -- status -- current_step -- result (JSON) -- created_at - -## Security Notes - -- Passwords are stored in plaintext (implement hashing for production) -- Use HTTPS in production -- Secure your API keys and environment variables -- Consider rate limiting for API endpoints diff --git a/app.py b/app.py deleted file mode 100644 index 1a87cb7..0000000 --- a/app.py +++ /dev/null @@ -1,2159 +0,0 @@ -from flask import Flask, render_template, request, jsonify, redirect, url_for, flash -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user -from flask_sqlalchemy import SQLAlchemy -from pyairtable import Api -import os -import json -import requests -from bs4 import BeautifulSoup -from datetime import datetime -from urllib.parse import urlparse -import threading -import logging -from logging.handlers import RotatingFileHandler - -app = Flask(__name__) -app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', os.urandom(24)) - -# Validate required environment variables -REQUIRED_ENV_VARS = ['AIRTABLE_PAT', 'SHUTTLE_AI_KEY'] -missing_vars = [var for var in REQUIRED_ENV_VARS if not os.environ.get(var)] -if missing_vars: - raise RuntimeError(f"Missing required environment variables: {', '.join(missing_vars)}") - -# Fix for Heroku postgres:// URLs (change to postgresql://) -database_url = os.environ.get('DATABASE_URL', 'sqlite:///vision.db') -if database_url.startswith('postgres://'): - database_url = database_url.replace('postgres://', 'postgresql://', 1) - -app.config['SQLALCHEMY_DATABASE_URI'] = database_url -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { - 'pool_size': 10, - 'pool_recycle': 3600, - 'pool_pre_ping': True -} -db = SQLAlchemy(app) -login_manager = LoginManager() -login_manager.init_app(app) -login_manager.login_view = 'login' - -# Configure logging -if not os.path.exists('logs'): - os.mkdir('logs') -file_handler = RotatingFileHandler('logs/vision.log', maxBytes=10240000, backupCount=10) -file_handler.setFormatter(logging.Formatter( - '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' -)) -file_handler.setLevel(logging.INFO) -app.logger.addHandler(file_handler) -app.logger.setLevel(logging.INFO) -app.logger.info('Hack Club Vision startup') - -# Helper function to normalize GitHub URLs -def normalize_github_url(url): - """ - Normalize GitHub URLs to the base repository URL. - Strips everything after github.com/owner/repo - - Examples: - - https://github.com/user/repo/tree/main -> https://github.com/user/repo - - https://github.com/user/repo/blob/main/README.md -> https://github.com/user/repo - - github.com/user/repo/ -> https://github.com/user/repo - """ - if not url: - return url - - try: - # Add https:// if missing - if not url.startswith('http'): - url = 'https://' + url - - # Parse the URL - parsed = urlparse(url) - - # Extract path and get owner/repo - path_parts = parsed.path.strip('/').split('/') - - if len(path_parts) >= 2: - owner = path_parts[0] - repo = path_parts[1] - # Return normalized URL - return f"https://github.com/{owner}/{repo}" - else: - # Return original if we can't parse it - return url - except Exception as e: - app.logger.warning(f"Failed to normalize GitHub URL '{url}': {e}") - return url - -# Global error handlers -@app.errorhandler(404) -def not_found_error(error): - return jsonify({'error': 'Not found'}), 404 - -@app.errorhandler(500) -def internal_error(error): - db.session.rollback() - app.logger.error(f'Server Error: {error}') - return jsonify({'error': 'Internal server error'}), 500 - -@app.errorhandler(Exception) -def handle_exception(e): - app.logger.error(f'Unhandled Exception: {str(e)}', exc_info=True) - return jsonify({'error': 'An unexpected error occurred'}), 500 - -# Initialize APIs -airtable_api = Api(os.environ.get('AIRTABLE_PAT')) - -# Add custom Jinja2 filter -@app.template_filter('fromjson') -def fromjson_filter(value): - return json.loads(value) - -# AI Helper - wrapper for ShuttleAI calls -def call_ai(prompt, max_tokens=8000, temperature=0.5): - """Wrapper for ShuttleAI calls using direct HTTP requests with retry logic for rate limits""" - import time - - api_key = os.environ.get('SHUTTLE_AI_KEY') - - headers = { - 'Authorization': f'Bearer {api_key}', - 'Content-Type': 'application/json' - } - - payload = { - 'model': 'anthropic/claude-sonnet-4-20250514', - 'messages': [{'role': 'user', 'content': prompt}], - 'temperature': temperature, - 'max_tokens': max_tokens - } - - max_retries = 30 # Maximum number of retry attempts for rate limits and server errors - retry_delay = 30 # Wait 30 seconds between retries - - for attempt in range(max_retries): - try: - # Timeout is max wait time (90s), not fixed wait - AI will respond as soon as ready - response = requests.post( - 'https://api.shuttleai.app/v1/chat/completions', - headers=headers, - json=payload, - timeout=90 - ) - - # Check for rate limit (429) or server error (500) - retry for both - if response.status_code == 429: - if attempt < max_retries - 1: - app.logger.warning(f"Rate limited by ShuttleAI (attempt {attempt + 1}/{max_retries}), retrying in {retry_delay}s...") - time.sleep(retry_delay) - continue - else: - raise Exception("Rate limit exceeded after 30 retries") - - if response.status_code == 500: - if attempt < max_retries - 1: - app.logger.warning(f"ShuttleAI server error 500 (attempt {attempt + 1}/{max_retries}), retrying in {retry_delay}s...") - time.sleep(retry_delay) - continue - else: - raise Exception("Server error 500 persisted after 30 retries") - - response.raise_for_status() - data = response.json() - content = data['choices'][0]['message']['content'].strip() - - # Extract JSON from markdown code blocks if present - if '```json' in content: - start = content.find('```json') + 7 - end = content.find('```', start) - content = content[start:end].strip() - elif '```' in content: - start = content.find('```') + 3 - end = content.find('```', start) - content = content[start:end].strip() - - return content - - except requests.exceptions.RequestException as e: - # Only retry if it's a rate limit error - if '429' in str(e) or 'rate' in str(e).lower(): - if attempt < max_retries - 1: - app.logger.warning(f"Request error (rate limit) (attempt {attempt + 1}/{max_retries}), retrying in {retry_delay}s...") - time.sleep(retry_delay) - continue - # For other errors, raise immediately - app.logger.error(f"AI request error: {e}") - raise - - except Exception as e: - app.logger.error(f"AI call error: {e}") - raise - - raise Exception("Rate limit: Maximum 30 retries exceeded") - -# Models -class User(UserMixin, db.Model): - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80), unique=True, nullable=False) - email = db.Column(db.String(120), unique=True, nullable=False) - password = db.Column(db.String(120), nullable=False) - verified = db.Column(db.Boolean, default=False, nullable=False) - bases = db.relationship('AirtableBase', backref='user', lazy=True) - -class AirtableBase(db.Model): - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - base_id = db.Column(db.String(120), nullable=False) - table_name = db.Column(db.String(120), nullable=False) - field_mappings = db.Column(db.Text) # JSON string - custom_instructions = db.Column(db.Text) # Custom review instructions - -class ApiKey(db.Model): - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - key = db.Column(db.String(128), unique=True, nullable=False) - name = db.Column(db.String(120), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - last_used = db.Column(db.DateTime) - is_active = db.Column(db.Boolean, default=True) - -class ReviewJob(db.Model): - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - base_id = db.Column(db.String(120), nullable=False) - table_name = db.Column(db.String(120), nullable=False) - record_id = db.Column(db.String(120), nullable=False) - status = db.Column(db.String(50), default='pending') # pending, running, completed, failed, cancelled - current_step = db.Column(db.String(200)) - result = db.Column(db.Text) # JSON string with final result - details = db.Column(db.Text) # JSON string with all steps - console_log = db.Column(db.Text) # JSON array of log messages with timestamps - created_at = db.Column(db.DateTime, default=datetime.utcnow) - completed_at = db.Column(db.DateTime) - cancel_requested = db.Column(db.Boolean, default=False) - - def to_dict(self): - """Convert job to dictionary for API responses""" - return { - 'id': self.id, - 'base_id': self.base_id, - 'table_name': self.table_name, - 'record_id': self.record_id, - 'status': self.status, - 'current_step': self.current_step, - 'result': json.loads(self.result) if self.result else None, - 'details': json.loads(self.details) if self.details else None, - 'console_log': json.loads(self.console_log) if self.console_log else [], - 'created_at': self.created_at.isoformat() if self.created_at else None, - 'completed_at': self.completed_at.isoformat() if self.completed_at else None - } - -@login_manager.user_loader -def load_user(user_id): - return User.query.get(int(user_id)) - -# Email Verification Functions -def generate_verification_code(): - """Generate a 6-digit verification code""" - import random - return str(random.randint(100000, 999999)) - -def send_verification_code_to_airtable(email, code): - """Send verification code to Airtable for email automation""" - try: - verification_base = airtable_api.base('appSUAc40CDu6bDAp') - verification_table = verification_base.table('Dashboard Email Verification') - - # Create record with email, code, and Pending status - verification_table.create({ - 'Email': email, - 'Code': code, - 'Status': 'Pending' - }) - - return True - except Exception as e: - app.logger.error(f'Failed to send verification code to Airtable: {str(e)}') - return False - -def verify_code_from_airtable(email, code): - """Verify the code from Airtable and update status to Verified""" - try: - verification_base = airtable_api.base('appSUAc40CDu6bDAp') - verification_table = verification_base.table('Dashboard Email Verification') - - # Search for matching email and code with Pending status - records = verification_table.all(formula=f"AND({{Email}}='{email}', {{Code}}='{code}', {{Status}}='Pending')") - - if records: - # Update status to Verified - verification_table.update(records[0]['id'], {'Status': 'Verified'}) - return True - - return False - except Exception as e: - app.logger.error(f'Failed to verify code from Airtable: {str(e)}') - return False - -# AI Helper Functions -def ai_detect_fields(table_records, log_fn=None): - """Use AI to detect which fields correspond to Code URL, Playable URL, etc.""" - if not table_records: - return None - - # Get sample data to help AI understand the fields - sample_record = table_records[0]['fields'] - field_names = list(sample_record.keys()) - - # Create field examples showing actual data - field_examples = {} - for field_name in field_names: - value = sample_record.get(field_name) - # Truncate long values - if isinstance(value, str) and len(value) > 100: - field_examples[field_name] = value[:100] + "..." - elif isinstance(value, (list, dict)): - field_examples[field_name] = str(value)[:100] - else: - field_examples[field_name] = value - - prompt = f"""You are analyzing an Airtable table to identify field mappings. - -AVAILABLE FIELDS WITH SAMPLE DATA: -{json.dumps(field_examples, indent=2)} - -TASK: Match each field to its purpose. Look at the SAMPLE DATA to understand what each field contains. - -FIELD PURPOSES TO MATCH: -1. "code_url" - GitHub repository link (look for github.com URLs) -2. "playable_url" - Live demo/project website link (look for deployed site URLs) -3. "hackatime_hours" - Number of hours worked (look for numbers like "10.5" or "15") -4. "auto_review_notes" - Internal review notes field (might be empty or have review text) -5. "auto_user_feedback" - User feedback field (might be empty or have feedback text) -6. "auto_review_tag" - Single select field with values like "Approved" or "Flagged" (review status) - -RULES: -- Use the EXACT field name from the list above -- If a field doesn't exist, use null -- Look at the sample data values to determine the correct field -- GitHub links go in code_url, deployed sites go in playable_url - -CRITICAL: Respond with ONLY valid JSON, no markdown, no code blocks, no explanations: -{{"code_url": "exact_field_name_or_null", "playable_url": "exact_field_name_or_null", "hackatime_hours": "exact_field_name_or_null", "auto_review_notes": "exact_field_name_or_null", "auto_user_feedback": "exact_field_name_or_null", "auto_review_tag": "exact_field_name_or_null"}}""" - - try: - if log_fn: - content = log_fn(prompt, max_tokens=8000, temperature=0.2, step_name="Field Detection") - else: - content = call_ai(prompt, max_tokens=8000, temperature=0.2) - - # Extract JSON from markdown if needed (same as other AI calls) - if '```json' in content: - start = content.find('```json') + 7 - end = content.find('```', start) - content = content[start:end].strip() - elif '```' in content: - start = content.find('```') + 3 - end = content.find('```', start) - content = content[start:end].strip() - - result = json.loads(content) - - # Convert null strings to None - for key in result: - if result[key] == 'null' or result[key] == 'None': - result[key] = None - - return result - except Exception as e: - print(f"Field detection error: {e}") - print(f"AI response was: {content if 'content' in locals() else 'No response'}") - return None - -def check_already_submitted(code_url, playable_url): - """Step 1: Enhanced duplicate check with thorough validation""" - try: - unified_base = airtable_api.base('app3A5kJwYqxMLOgh') - unified_table = unified_base.table('Approved Projects') - - # Normalize URLs for better matching - def normalize_url(url): - if not url: - return None - url = url.lower().strip() - # Remove trailing slashes, www, http/https for comparison - url = url.rstrip('/').replace('https://', '').replace('http://', '').replace('www.', '') - return url - - norm_code = normalize_url(code_url) - norm_play = normalize_url(playable_url) - - # Get all records and check manually for better matching - all_records = unified_table.all() - - for record in all_records: - fields = record['fields'] - existing_code = normalize_url(fields.get('Code URL', '')) - existing_play = normalize_url(fields.get('Playable URL', '')) - - # Check for exact matches - if norm_code and existing_code == norm_code: - return True - if norm_play and existing_play == norm_play: - return True - - # Check if GitHub repo matches (same owner/repo) - if norm_code and existing_code: - if 'github.com' in norm_code and 'github.com' in existing_code: - code_parts = norm_code.split('github.com/')[-1].split('/')[:2] - existing_parts = existing_code.split('github.com/')[-1].split('/')[:2] - if len(code_parts) == 2 and len(existing_parts) == 2: - if code_parts[0] == existing_parts[0] and code_parts[1] == existing_parts[1]: - return True - - return False - except Exception as e: - print(f"Error checking unified DB: {e}") - raise - -def test_project(code_url, log_fn=None): - """Step 2: Deep code analysis - HTML/CSS/JS inspection with smart crawling""" - import time - import re - max_retries = 3 - retry_delay = 5 - - for attempt in range(max_retries): - try: - # Fetch the main website content - response = requests.get(code_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'}) - break # Success, exit retry loop - except Exception as e: - if attempt < max_retries - 1: - app.logger.warning(f"Failed to fetch website (attempt {attempt + 1}/{max_retries}): {e}") - time.sleep(retry_delay) - continue - else: - # All retries failed - can't access the site - raise Exception(f"Unable to access website after {max_retries} attempts. The site may be down, private, or blocking automated access: {str(e)}") - - try: - soup = BeautifulSoup(response.text, 'html.parser') - page_html = response.text - - # ========== DEEP CODE ANALYSIS ========== - - # Extract ALL CSS (inline, internal, and count external) - css_code = [] - css_external = 0 - - # Inline styles - for tag in soup.find_all(style=True): - css_code.append(tag['style']) - - # Internal - - - - - - -{% endblock %} diff --git a/templates/jobs.html b/templates/jobs.html deleted file mode 100644 index 07d8ddf..0000000 --- a/templates/jobs.html +++ /dev/null @@ -1,908 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
-
-

- - Review Jobs -

-

Monitor running and completed review jobs

-
- -
- - -
-
-

- - Running Jobs - 0 -

-
-
-
- -

No jobs currently running

-
-
-
- - -
-
-

- - Recent Jobs - 0 -

-
-
-
- -

No completed jobs yet

-
-
-
-
- - - - - - - - - - -{% endblock %} diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index b1eaa4e..0000000 --- a/templates/login.html +++ /dev/null @@ -1,98 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
-
-
- -
-

- Hack Club Vision -

-

- Sign in to your account -

-
-
-
-
- - -
-
- - -
-
- - - -
- -
-
- -
-
- - -{% endblock %} diff --git a/templates/register.html b/templates/register.html deleted file mode 100644 index d79e567..0000000 --- a/templates/register.html +++ /dev/null @@ -1,205 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
-
-
- -
-

- Create Account -

-

- Register for Hack Club Vision -

-
- -
-
-
- - -
-
- - -
-
- - -
-
- - - -
- -
-
- - - - -
-
- - -{% endblock %} diff --git a/uv.lock b/uv.lock deleted file mode 100644 index da62a11..0000000 --- a/uv.lock +++ /dev/null @@ -1,7 +0,0 @@ -version = 1 -requires-python = ">=3.11" - -[[package]] -name = "python-template" -version = "0.1.0" -source = { virtual = "." }