diff --git a/.gitignore b/.gitignore index ad03605..92cc096 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,21 @@ -# Vault is symlinked, not owned by this workspace -vault/ +# Logs +logs +*.log +npm-debug.log* node_modules/ -.DS_Store +.env +.env.development.local +.env.test.local +.env.production.local +.env.local +.next +out +dist +coverage *.sqlite *.db +.DS_Store Thumbs.db +vault/ +.openclaw/ +memory/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..76f4e59 --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +# WardrobeAI + +> Clothes from real designers. Picked for you. On your budget. + +WardrobeAI is a curated clothing subscription service that sources from small businesses and boutique brands. We learn your personal style through a real conversation (not a multiple-choice quiz), match pieces within your monthly budget, and get better with every box based on your feedback. + +## The Problem We're Solving + +**For customers:** +- Shopping is overwhelming — thousands of options, no curation +- Small designers are hard to find (buried on Etsy, hard to discover on Instagram) +- Mass-market subscriptions feel generic — same brands everyone else wears +- "Style quizzes" don't capture the nuances of your body and life +- The feedback you give doesn't seem to change what you get + +**For small designers:** +- Hard to reach customers without massive ad budgets +- Wholesale buyers require large minimum orders +- No direct feedback on why items don't sell + +## Our Approach + +| What Others Do | What We Do | +|---|---| +| Source from mass-market brands (Stitch Fix, Nordstrom) | Source from small businesses and boutique designers first | +| Per-item pricing ($40-80 per piece) | Monthly budget you choose ($100 or $250) | +| Multiple-choice style quiz | Real conversation — style interview with a human feel | +| "Style Shuffle" that never learns | Structured feedback loop that actually improves | +| One-size-fits-all | "Invariants" — your hard lines, honored (no florals, no synthetics, etc.) | +| Off-the-rack only | AI design-on-demand: "I want this shape in that fabric" — custom made | + +## Product Tiers + +### Essential — $100/mo +- 3-5 clothing items per month +- From small businesses and boutique brands +- Style interview and feedback loop included +- Shoes, bags, jewelry available as add-ons + +### Signature — $250/mo +- 4-8 clothing items per month +- Higher-end boutique brands +- Early access to new designers +- Concierge-level style interview + +### Custom — Variable +- You set the budget +- You choose the components (clothing only, or full wardrobe) +- Custom frequency (monthly, quarterly, etc.) +- Priority access to AI design-on-demand + +## Market Opportunity + +- **$7 billion** global fashion subscription market +- Growing at **6.1% CAGR** (2025-2034) +- No incumbent dominates the "small business boutique" niche +- Stitch Fix is mass market; Rent the Runway is rental; no one is budget-capped with boutique curation + +## Competitive Landscape + +| Company | Model | Our Differentiation | +|---|---|---| +| **Stitch Fix** ($1.6B market cap) | AI + human stylist, mass brands | We source from small businesses; we have budget caps | +| **Wantable** | Curated edits, $20 styling fee | We have a real conversation, not a quiz; our feedback loop learns | +| **DailyLook** | Premium styling, $40 styling fee | We start at $100/mo, not premium; we honor hard constraints | +| **Dia & Co** | Plus size niche | We're size-inclusive but not niche-restricted | +| **Rent the Runway** | Rental subscription | We sell, not rent; you keep what you love | + +**The niche we own:** Small business curation with budget caps and real learning. + +## Technology Stack + +- **Frontend:** Next.js (React), Tailwind CSS, mobile-first +- **Backend:** Node.js or Python (FastAPI), PostgreSQL + Redis +- **AI/ML:** Python service for curation; LLM integration for style interview and invariants parsing +- **Payments:** Stripe (subscription billing) +- **Hosting:** Vercel (frontend) + Railway/Fly.io (backend) +- **Analytics:** Umami (self-hosted, already deployed) +- **Infrastructure:** Storj for static assets, potential 3PL for warehousing (Phase 2) + +## Repository Structure + +``` +wardrobe-ai/ +├── brand/ # Brand identity, positioning, customer personas +├── business/ # Pricing, financial model, vendor targets, roadmap +├── engineering/ # Architecture, curation engine, tech docs +├── legal/ # Compliance, terms, policies +├── marketing/ # Social strategy, content calendar, viral playbook +├── operations/ # Return logistics, shipping, warehouse transition +├── product/ # Style interview, feedback system, invariants, landing page +├── research/ # Competitive analysis, market data +└── site/ # Landing page source (Astro SSG) +``` + +## Roadmap + +### Phase 0: Foundation (Months 1-2) +- Brand identity finalized +- 10-20 customer discovery interviews +- 5-10 vendor partners identified +- Landing page + waitlist live +- Legal structure + insurance + +**Goal:** 50+ waitlist signups, 3+ vendor commitments + +### Phase 1: MVP (Months 3-5) +- 20 founding customers (friends, family, early signups) +- Essential tier only ($100/mo) +- Manual curation with AI assistance +- Web app: style interview + feedback +- Manual returns processing + +**Goal:** 60%+ keep rate, NPS 40+ + +### Phase 2: Growth (Months 6-9) +- Signature tier launch ($250/mo) +- AI curation engine v2 +- TikTok + Instagram content machine +- Referral program +- 100 subscribers + +**Goal:** 65%+ keep rate, 10%+ MoM organic growth + +### Phase 3: Scale (Months 10-18) +- 500+ subscribers +- AI design-on-demand pilot +- Warehousing or 3PL transition +- 30+ active vendors +- Mobile app + +**Goal:** 70%+ keep rate, positive unit economics + +### Phase 4: Expansion (Months 18-36) +- 2000+ subscribers +- Men's clothing vertical +- Plus-size specialization +- AI design-on-demand full launch +- Potential Series A or profitable bootstrapping + +## Issues & Project Management + +All work is tracked via [Forgejo Issues](https://os.scoble.me/forgejo/sscoble/wardrobe-ai/issues). + +**Labels:** +- `market` — Market research, competitive analysis, customer personas +- `feature` — Product features and design +- `engineering` — Technical implementation and architecture +- `business` — Pricing, financials, vendor partnerships +- `legal` — Compliance, terms, policies +- `improvement` — Documentation and process improvements + +## Why This Matters + +Fashion is personal. It's how we present ourselves to the world. But the current options are either overwhelming (do-it-yourself shopping) or generic (mass-market subscriptions). + +We believe there's a middle path: curated discovery from real designers, within a budget that makes sense, with a system that actually learns what works for *you*. + +Supporting small businesses while helping people feel like themselves. That's WardrobeAI. + +--- + +*Work in progress. Phase 0 — validating demand and building the foundation.* + +Commit signing enabled: Sat May 2 12:47:38 PM PDT 2026 diff --git a/wardrobe-ai/.env.example b/wardrobe-ai/.env.example new file mode 100644 index 0000000..3468651 --- /dev/null +++ b/wardrobe-ai/.env.example @@ -0,0 +1,12 @@ +# Backend Configuration +PORT=3001 +NODE_ENV=development + +# Database +DATABASE_PATH=./wardrobe.db + +# Amazon Affiliate Configuration +AMAZON_ASSOCIATE_ID=greybeardhold-20 + +# Frontend Configuration +NEXT_PUBLIC_API_URL=http://localhost:3001/api diff --git a/wardrobe-ai/.gitignore b/wardrobe-ai/.gitignore new file mode 100644 index 0000000..70ad169 --- /dev/null +++ b/wardrobe-ai/.gitignore @@ -0,0 +1,32 @@ +# Dependencies +node_modules/ + +# Environment variables +.env + +# Build outputs +.next/ +out/ +build/ +dist/ + +# Database +*.db + +# Logs +*.log +logs/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Temporary files +tmp/ +temp/ diff --git a/wardrobe-ai/DEPLOY.md b/wardrobe-ai/DEPLOY.md new file mode 100644 index 0000000..e8b63fb --- /dev/null +++ b/wardrobe-ai/DEPLOY.md @@ -0,0 +1,252 @@ +# WardrobeAI - Deployment Guide + +## Quick Deployment (Local Testing) + +### Prerequisites +- Node.js v18+ +- npm + +### Steps + +1. **Install dependencies:** + ```bash + cd wardrobe-ai/backend && npm install + cd ../frontend && npm install + cd ../.. && npm install concurrently --save-dev + ``` + +2. **Initialize database:** + ```bash + cd backend + node init-db.js + ``` + +3. **Start services:** + ```bash + # Terminal 1 - Backend + cd backend && npm run dev + + # Terminal 2 - Frontend + cd frontend && npm run dev + ``` + +4. **Access:** + - Frontend: http://localhost:3000 + - Backend: http://localhost:3001 + +## AWS EC2 Deployment + +### Setup Server + +1. **Launch EC2 instance:** + - AMI: Ubuntu 22.04 + - Instance type: t3.micro (free tier eligible) + - Security group: Allow ports 80, 443, 3001 + +2. **Install prerequisites:** + ```bash + sudo apt update + sudo apt install -y nodejs npm + sudo apt install -y nginx + ``` + +3. **Deploy application:** + ```bash + # Clone or copy the project + git clone ~/wardrobe-ai + cd ~/wardrobe-ai/backend + npm install + node init-db.js + ``` + +4. **Run backend (production):** + ```bash + cd ~/wardrobe-ai/backend + npm start & + ``` + +5. **Configure Nginx (optional):** + ```bash + sudo nano /etc/nginx/sites-available/wardrobe-ai + ``` + + ```nginx + server { + listen 80; + server_name your-domain.com; + + location /api/ { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } + ``` + + ```bash + sudo ln -s /etc/nginx/sites-available/wardrobe-ai /etc/nginx/sites-enabled/ + sudo nginx -t + sudo systemctl restart nginx + ``` + +## Docker Deployment + +### Create docker-compose.yml +```yaml +version: '3.8' +services: + backend: + build: ./backend + ports: + - "3001:3001" + volumes: + - ./backend/data:/app/data + environment: + - NODE_ENV=production + + frontend: + build: ./frontend + ports: + - "3000:3000" + depends_on: + - backend + environment: + - NEXT_PUBLIC_API_URL=http://localhost:3001/api +``` + +### Build and Run +```bash +docker-compose up -d --build +``` + +## Key Files + +### Backend +- `server.js` - Express API server +- `init-db.js` - Database initialization +- `wardrobe.db` - SQLite database (auto-created) + +### Frontend +- `next.config.js` - Next.js configuration +- `src/pages/index.js` - Style profile form +- `src/pages/box.js` - Curated box display +- `src/components/CuratedBox.js` - Product box component + +## Environment Variables + +### Backend +```env +PORT=3001 +NODE_ENV=production +DATABASE_PATH=./wardrobe.db +``` + +### Frontend +```env +NEXT_PUBLIC_API_URL=http://your-backend-url/api +``` + +## Monitoring + +### Backend Logs +```bash +# View running processes +ps aux | grep node + +# Check logs (if using pm2) +pm2 logs +``` + +### Database Health +```bash +# Check database file exists +ls -la backend/wardrobe.db + +# Verify products count +sqlite3 backend/wardrobe.db "SELECT COUNT(*) FROM products;" +``` + +## Troubleshooting + +### Port Already in Use +```bash +# Find process using port 3001 +lsof -i :3001 + +# Kill it +kill -9 +``` + +### Database Corruption +```bash +# Delete and reinitialize +rm backend/wardrobe.db +cd backend && node init-db.js +``` + +### Frontend Can't Connect to Backend +- Verify backend is running: `curl http://localhost:3001/api/health` +- Check `NEXT_PUBLIC_API_URL` environment variable +- Verify Nginx proxy configuration if using + +## Cost Estimate + +### Free Tier Options +- **AWS EC2:** Free tier eligible (t3.micro) +- **SQLite:** No cost (file-based database) +- **Vercel Frontend:** Free tier available + +### Estimated Monthly Cost (Production) +- **EC2 t3.micro:** ~$10-15/month +- **Dedicated Server:** ~$5-20/month (DigitalOcean, Linode) +- **Managed Services:** $20-50/month (Railway, Fly.io) + +## Maintenance + +### Regular Tasks +1. **Database backup:** Copy `wardrobe.db` periodically +2. **Monitor logs:** Check for errors in production +3. **Product updates:** Add new products as needed +4. **Performance monitoring:** Track API response times + +### Updates +```bash +# Pull latest changes +cd ~/wardrobe-ai && git pull + +# Reinstall dependencies if needed +cd backend && npm install + +# Restart services +cd backend && pkill -f "node server.js" +cd backend && npm start & +``` + +## Support + +For issues, check: +1. Backend logs +2. Frontend browser console +3. Database integrity +4. Network connectivity + +## Next Steps + +After deployment: +1. Set up monitoring (e.g., PM2, CloudWatch) +2. Configure SSL (Let's Encrypt) +3. Set up database backups +4. Add user authentication +5. Implement analytics diff --git a/wardrobe-ai/DEVGUIDE.md b/wardrobe-ai/DEVGUIDE.md new file mode 100644 index 0000000..b3cbffe --- /dev/null +++ b/wardrobe-ai/DEVGUIDE.md @@ -0,0 +1,228 @@ +# WardrobeAI - Development Guide + +## Quick Start + +### Prerequisites +- Node.js (v18 or higher) +- npm + +### Setup + +1. **Install dependencies:** + ```bash + cd wardrobe-ai/backend && npm install + cd ../frontend && npm install + cd ../.. && npm install concurrently --save-dev + ``` + +2. **Initialize the database:** + ```bash + cd backend + node init-db.js + ``` + +3. **Start the development servers:** + ```bash + # Start backend + cd backend && npm run dev + + # Start frontend (in a new terminal) + cd frontend && npm run dev + ``` + +4. **Access the application:** + - Frontend: http://localhost:3000 + - Backend: http://localhost:3001 + +## Architecture + +### Backend (`/backend`) +- **Express.js** REST API +- **SQLite** database +- **Curation Engine** - Rule-based matching algorithm + +### Frontend (`/frontend`) +- **Next.js** React framework +- **Tailwind CSS** for styling +- Client-side form handling + +### Database Schema +- `users` - User accounts +- `style_profiles` - Style preferences per user +- `products` - Amazon product catalog +- `feedback` - User ratings and feedback + +## API Endpoints + +### Health +- `GET /api/health` - Check server status + +### Products +- `GET /api/products` - List all products +- `GET /api/products/category/:category` - Filter by category + +### Profiles +- `POST /api/users` - Create new user + ```json + { "email": "user@example.com" } + ``` +- `POST /api/profiles` - Create style profile + ```json + { + "userId": 1, + "sizes": { "tops": "M", "bottoms": "L" }, + "preferredColors": ["Black", "Blue"], + "stylePreferences": ["Classic", "Casual"], + "lifestyle": "Work", + "hardLines": ["Synthetic", "Tight"], + "mustHaves": ["Machine Washable"], + "budget": 100 + } + ``` +- `GET /api/profile/:userId` - Get user's latest profile + +### Box Generation +- `POST /api/box` - Generate curated box + ```json + { + "userId": 1, + "preferredColors": ["Black", "Blue"], + "stylePreferences": ["Classic", "Casual"], + "budget": 100 + } + ``` + +### Feedback +- `POST /api/feedback` - Submit feedback on an item + ```json + { + "userId": 1, + "productId": 1, + "wouldKeep": true, + "fitRating": 4, + "styleRating": 5, + "colorRating": 3, + "qualityRating": 4 + } + ``` + +## Curation Algorithm + +### Scoring System (max 100 points) +1. **Color Match** (+25 points) + - Product color matches user's preferred colors + +2. **Style Match** (+35 points) + - Product style tags match user's preferences + +3. **Category Fit** (+20 points) + - Based on wardrobe gaps specified by user + +4. **Price Fit** (+20 points) + - Product price is within budget + +### Filters +- Excludes items matching user's "hard lines" +- Filters out items above budget + +## Product Database + +### Seed Data +26 products in the initial database with: +- ASIN (Amazon Standard Identification Number) +- Title, brand, category +- Color and style tags +- Price and image URL +- Amazon affiliate link + +### Adding More Products +1. Add product data to `seedProducts` array in `init-db.js` +2. Re-run initialization: `node init-db.js` + +## Tech Stack + +### Backend +- **Express.js** - Web framework +- **SQLite** - Database +- **Node.js** - Runtime + +### Frontend +- **Next.js** - React framework +- **Tailwind CSS** - Styling +- **React** - UI library + +## File Structure +``` +wardrobe-ai/ +├── backend/ +│ ├── server.js # Express server +│ ├── init-db.js # Database initialization +│ └── wardrobe.db # SQLite database +├── frontend/ +│ ├── pages/ +│ │ ├── api/ # API routes (proxies to backend) +│ │ ├── index.js # Style profile form +│ │ └── box.js # Curated box display +│ └── components/ +│ ├── StyleProfileForm.js +│ └── CuratedBox.js +└── package.json +``` + +## Deployment + +### Backend (Railway/Fly.io) +1. Deploy Express server +2. Set up SQLite database +3. Configure environment variables + +### Frontend (Vercel) +1. Deploy Next.js application +2. Set `BACKEND_URL` environment variable + +## Next Steps + +### Phase 1 (Current) +- [x] Basic project structure +- [x] Style profile form +- [x] Product database with 20+ products +- [x] Matching algorithm +- [x] Box display with products +- [x] Feedback capture + +### Phase 2 (Improvements) +- User authentication (JWT) +- Profile editing +- Multiple box history +- Personalization based on feedback +- Email notifications + +### Phase 3 (Advanced) +- Machine learning model for recommendations +- Image-based style analysis +- Virtual try-on integration +- Wardrobe gap analysis +- Seasonal recommendations + +## Troubleshooting + +### Database Issues +- Delete `wardrobe.db` and re-run `init-db.js` + +### API Proxy Issues +- Check Next.js rewrites in `next.config.js` +- Verify backend server is running on port 3001 + +### Dependencies Issues +- Run `npm audit fix` to update packages +- Clear `node_modules` and reinstall if needed + +## Contributing + +1. Create issues for bugs/features +2. Test changes locally before committing +3. Update documentation for new features + +## License + +MIT \ No newline at end of file diff --git a/wardrobe-ai/README.md b/wardrobe-ai/README.md new file mode 100644 index 0000000..9bdb44f --- /dev/null +++ b/wardrobe-ai/README.md @@ -0,0 +1,24 @@ +# WardrobeAI MVP + +A personalized curation engine for women's clothing recommendations from Amazon. + +## Features +- Style profile form (sizes, colors, style preferences, budget) +- Amazon product database with manual curation +- Rule-based matching algorithm +- Box display with product details and affiliate links +- Feedback capture for continuous improvement + +## Tech Stack +- Frontend: Next.js + Tailwind CSS +- Backend: Node.js + Express +- Database: SQLite +- Deploy: Vercel (frontend) + Railway/Fly.io (backend) + +## Setup + +```bash +cd wardrobe-ai +npm install +npm run dev +``` diff --git a/wardrobe-ai/backend/init-db.js b/wardrobe-ai/backend/init-db.js new file mode 100644 index 0000000..0b492cc --- /dev/null +++ b/wardrobe-ai/backend/init-db.js @@ -0,0 +1,133 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// Amazon Affiliate Configuration +const AFFILIATE_ID = process.env.AMAZON_ASSOCIATE_ID || 'greybeardhold-20'; + +const db = new sqlite3.Database(path.join(__dirname, 'wardrobe.db'), (err) => { + if (err) { + console.error('Error opening database:', err); + } else { + console.log('Connected to SQLite database'); + initializeDatabase(); + } +}); + +function initializeDatabase() { + db.serialize(() => { + // Create tables + db.run(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + db.run(` + CREATE TABLE IF NOT EXISTS style_profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + sizes TEXT, + preferred_colors TEXT, + style_preferences TEXT, + lifestyle TEXT, + hard_lines TEXT, + must_haves TEXT, + budget INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + + db.run(` + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + asin TEXT UNIQUE NOT NULL, + title TEXT, + brand TEXT, + category TEXT, + color TEXT, + style_tags TEXT, + price REAL, + image_url TEXT, + affiliate_link TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + db.run(` + CREATE TABLE IF NOT EXISTS feedback ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + would_keep INTEGER, + fit_rating INTEGER, + style_rating INTEGER, + color_rating INTEGER, + quality_rating INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + + // Load seed products + loadSeedProducts(); + }); +} + +function loadSeedProducts() { + const seedProducts = [ + { asin: 'B08N5KWM3S', title: 'Hanes Women\'s EcoFleece Oversized Hoodie', brand: 'Hanes', category: 'Tops', color: 'Grey', style_tags: 'casual,comfortable,cozy', price: 22.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X9L.jpg', affiliate_link: `https://www.amazon.com/dp/B08N5KWM3S?tag=${AFFILIATE_ID}` }, + { asin: 'B09Q5G7X1J', title: 'Gap Women\'s Classic French Terry Sweatpants', brand: 'Gap', category: 'Bottoms', color: 'Grey', style_tags: 'casual,comfortable,classic', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09Q5G7X1J?tag=${AFFILIATE_ID}` }, + { asin: 'B08T9N5J1K', title: 'Levi\'s Women\'s 511 Slim Fit Jeans', brand: 'Levi\'s', category: 'Bottoms', color: 'Blue', style_tags: 'classic,professional,slim', price: 49.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08T9N5J1K?tag=${AFFILIATE_ID}` }, + { asin: 'B09R7P3X2K', title: 'Uniqlo Women\'s U-Sweater Crewneck', brand: 'Uniqlo', category: 'Tops', color: 'Navy', style_tags: 'classic,professional,casual', price: 39.90, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09R7P3X2K?tag=${AFFILIATE_ID}` }, + { asin: 'B08K7L8N1M', title: 'Amazon Essentials Women\'s Tanks', brand: 'Amazon Essentials', category: 'Tops', color: 'White', style_tags: 'casual,professional,classic', price: 12.99, image_url: 'https://m.media-amazon.com/images/I/51Z5J5X5X1L.jpg', affiliate_link: `https://www.amazon.com/dp/B08K7L8N1M?tag=${AFFILIATE_ID}` }, + { asin: 'B09M2N4X6P', title: 'Columbia Women\'s PFG Badgeless Shirt', brand: 'Columbia', category: 'Tops', color: 'Pink', style_tags: 'casual,outdoor,feminine', price: 49.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X2L.jpg', affiliate_link: `https://www.amazon.com/dp/B09M2N4X6P?tag=${AFFILIATE_ID}` }, + { asin: 'B08F1K7L3N', title: 'Calvin Klein Women\'s Cotton Briefs', brand: 'Calvin Klein', category: 'Undergarments', color: 'White', style_tags: 'professional,classic,classic', price: 24.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B08F1K7L3N?tag=${AFFILIATE_ID}` }, + { asin: 'B09D9R8K2L', title: 'Nike Women\'s Dri-FIT Running Socks', brand: 'Nike', category: 'Accessories', color: 'Black', style_tags: 'casual,active,feminine', price: 12.00, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B09D9R8K2L?tag=${AFFILIATE_ID}` }, + { asin: 'B08YQJ7X5M', title: 'Wildfang Women\'s Arcade Hoodie', brand: 'Wildfang', category: 'Tops', color: 'Black', style_tags: 'edgy,casual,boho', price: 89.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B08YQJ7X5M?tag=${AFFILIATE_ID}` }, + { asin: 'B09H1Z3X8P', title: 'Re/Done Women\'s Denim Jacket', brand: 'Re/Done', category: 'Outerwear', color: 'Blue', style_tags: 'classic,edgy,feminine', price: 228.00, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09H1Z3X8P?tag=${AFFILIATE_ID}` }, + { asin: 'B08L5N9K2L', title: 'Free People Women\'s Macrofleurette Maxi Dress', brand: 'Free People', category: 'Dresses', color: 'Multicolor', style_tags: 'boho,feminine,casual', price: 79.00, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08L5N9K2L?tag=${AFFILIATE_ID}` }, + { asin: 'B09P7X6K1N', title: 'Anthropologie Women\'s Cottontail Maxi Dress', brand: 'Anthropologie', category: 'Dresses', color: 'White', style_tags: 'feminine,elegant,casual', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09P7X6K1N?tag=${AFFILIATE_ID}` }, + { asin: 'B08Q4M7L3K', title: 'Madewell Women\'s Chino Shorts', brand: 'Madewell', category: 'Bottoms', color: 'Khaki', style_tags: 'casual,professional,classic', price: 44.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X1L.jpg', affiliate_link: `https://www.amazon.com/dp/B08Q4M7L3K?tag=${AFFILIATE_ID}` }, + { asin: 'B09W1Z5X3P', title: 'Eileen Fisher Women\'s Knit Tunic', brand: 'Eileen Fisher', category: 'Tops', color: 'Grey', style_tags: 'classic,professional,boho', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B09W1Z5X3P?tag=${AFFILIATE_ID}` }, + { asin: 'B08J7K9L2N', title: 'Talbots Women\'s Cashmere Blend Sweater', brand: 'Talbots', category: 'Tops', color: 'Burgundy', style_tags: 'classic,professional,elegant', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B08J7K9L2N?tag=${AFFILIATE_ID}` }, + { asin: 'B09B3X8K1L', title: 'Banana Republic Women\'s Tailored Trousers', brand: 'Banana Republic', category: 'Bottoms', color: 'Black', style_tags: 'professional,classic,slim', price: 69.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B09B3X8K1L?tag=${AFFILIATE_ID}` }, + { asin: 'B08X7N4K2M', title: 'J.Crew Women\'s Lucid Sateen Pants', brand: 'J.Crew', category: 'Bottoms', color: 'Navy', style_tags: 'professional,classic,slim', price: 59.99, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X2L.jpg', affiliate_link: `https://www.amazon.com/dp/B08X7N4K2M?tag=${AFFILIATE_ID}` }, + { asin: 'B09F2K6L1N', title: 'Old Navy Women\'s Power Stretch Jeans', brand: 'Old Navy', category: 'Bottoms', color: 'Blue', style_tags: 'casual,comfortable,classic', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09F2K6L1N?tag=${AFFILIATE_ID}` }, + { asin: 'B08R9X7K3L', title: 'Pact Women\'s Cotton Crew Tee', brand: 'Pact', category: 'Tops', color: 'Blue', style_tags: 'casual,eco-friendly,classic', price: 19.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B08R9X7K3L?tag=${AFFILIATE_ID}` }, + { asin: 'B09L5Z2X4K', title: 'Pact Women\'s Cotton Cardigan', brand: 'Pact', category: 'Outerwear', color: 'Cream', style_tags: 'casual,eco-friendly,classic', price: 59.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09L5Z2X4K?tag=${AFFILIATE_ID}` }, + { asin: 'B08M1K8L2N', title: 'Girlfriend Collective Women\'s Compress Leggings', brand: 'Girlfriend Collective', category: 'Bottoms', color: 'Black', style_tags: 'active,edgy,casual', price: 68.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B08M1K8L2N?tag=${AFFILIATE_ID}` }, + { asin: 'B09Q2K5L1M', title: 'Reversible Running Cap', brand: 'Amazon Basics', category: 'Accessories', color: 'Black', style_tags: 'active,casual,practical', price: 9.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B09Q2K5L1M?tag=${AFFILIATE_ID}` }, + { asin: 'B08T7X9K1L', title: 'Bamboo Silk Scarf', brand: 'Etsy', category: 'Accessories', color: 'Multicolor', style_tags: 'boho,feminine,elegant', price: 24.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B08T7X9K1L?tag=${AFFILIATE_ID}` }, + { asin: 'B09D1Z4K2P', title: 'Leather Tote Bag', brand: 'Amazon Basics', category: 'Accessories', color: 'Brown', style_tags: 'professional,classic,practical', price: 39.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B09D1Z4K2P?tag=${AFFILIATE_ID}` }, + { asin: 'B08W3K7L1N', title: 'Sunglasses - Oversized', brand: 'Amazon Basics', category: 'Accessories', color: 'Black', style_tags: 'feminine,edgy,casual', price: 14.99, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B08W3K7L1N?tag=${AFFILIATE_ID}` }, + { asin: 'B09H5X2K3L', title: 'Beaded Necklace Set', brand: 'Etsy', category: 'Accessories', color: 'Multicolor', style_tags: 'boho,feminine,elegant', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X9L.jpg', affiliate_link: `https://www.amazon.com/dp/B09H5X2K3L?tag=${AFFILIATE_ID}` } + ]; + + seedProducts.forEach(product => { + db.run( + `INSERT OR IGNORE INTO products (asin, title, brand, category, color, style_tags, price, image_url, affiliate_link) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + product.asin, + product.title, + product.brand, + product.category, + product.color, + JSON.stringify(product.style_tags), + product.price, + product.image_url, + product.affiliate_link + ], + (err) => { + if (err) console.error('Error inserting product:', err); + } + ); + }); + + console.log('Database initialized with', seedProducts.length, 'products'); + db.close(); +} diff --git a/wardrobe-ai/backend/package-lock.json b/wardrobe-ai/backend/package-lock.json new file mode 100644 index 0000000..a1f5090 --- /dev/null +++ b/wardrobe-ai/backend/package-lock.json @@ -0,0 +1,2388 @@ +{ + "name": "wardrobe-ai-backend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wardrobe-ai-backend", + "version": "0.1.0", + "dependencies": { + "express": "^4.18.2", + "joi": "^18.1.2", + "sqlite3": "^5.1.6" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@hapi/address": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/formula": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/tlds": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", + "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "optional": true + }, + "node_modules/joi": { + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", + "integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/address": "^5.1.1", + "@hapi/formula": "^3.0.2", + "@hapi/hoek": "^11.0.7", + "@hapi/pinpoint": "^2.0.1", + "@hapi/tlds": "^1.1.1", + "@hapi/topo": "^6.0.2", + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.90.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", + "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + } + } +} diff --git a/wardrobe-ai/backend/package.json b/wardrobe-ai/backend/package.json new file mode 100644 index 0000000..f40e44a --- /dev/null +++ b/wardrobe-ai/backend/package.json @@ -0,0 +1,16 @@ +{ + "name": "wardrobe-ai-backend", + "version": "0.1.0", + "description": "WardrobeAI backend API", + "main": "server.js", + "scripts": { + "dev": "node server.js", + "start": "node server.js", + "init-db": "node init-db.js" + }, + "dependencies": { + "express": "^4.18.2", + "joi": "^18.1.2", + "sqlite3": "^5.1.6" + } +} diff --git a/wardrobe-ai/backend/server.js b/wardrobe-ai/backend/server.js new file mode 100644 index 0000000..e86cc9a --- /dev/null +++ b/wardrobe-ai/backend/server.js @@ -0,0 +1,503 @@ +const express = require('express'); +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); +const crypto = require('crypto'); +const { sanitizeBody, validate } = require('./src/middleware/validation'); + +// Amazon Affiliate Configuration +const AFFILIATE_ID = process.env.AMAZON_ASSOCIATE_ID || 'greybeardhold-20'; + +const app = express(); + +// Middleware +app.use(express.json()); +app.use(sanitizeBody); +app.use(express.static(path.join(__dirname, '../frontend/public'))); +app.use((req, res, next) => { + const allowedOrigins = ['http://localhost:3000', 'https://wardrobe.scoble.me']; + const origin = req.headers.origin || ''; + + if (allowedOrigins.includes(origin)) { + res.header('Access-Control-Allow-Origin', origin); + } + + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + if (req.method === 'OPTIONS') { + return res.status(200).end(); + } + next(); +}); + +// Database initialization +const db = new sqlite3.Database(path.join(__dirname, 'wardrobe.db'), (err) => { + if (err) { + console.error('Error opening database:', err); + } else { + console.log('Connected to SQLite database'); + } +}); + +// Create tables +db.serialize(() => { + // Users table + db.run(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + password_hash TEXT, + password_salt TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Style profiles table + db.run(` + CREATE TABLE IF NOT EXISTS style_profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + sizes TEXT, + preferred_colors TEXT, + style_preferences TEXT, + lifestyle TEXT, + hard_lines TEXT, + must_haves TEXT, + budget INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + + // Products table + db.run(` + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + asin TEXT UNIQUE NOT NULL, + title TEXT, + brand TEXT, + category TEXT, + color TEXT, + style_tags TEXT, + price REAL, + image_url TEXT, + affiliate_link TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Feedback table + db.run(` + CREATE TABLE IF NOT EXISTS feedback ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + would_keep INTEGER, + fit_rating INTEGER, + style_rating INTEGER, + color_rating INTEGER, + quality_rating INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + // Auth tokens table + db.run(` + CREATE TABLE IF NOT EXISTS auth_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + token TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); +}); + +// ─── Auth helpers ─── +const generateToken = () => crypto.randomBytes(32).toString('hex'); + +const authenticate = (req, res, next) => { + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) return res.status(401).json({ error: 'Authentication required' }); + + db.get('SELECT user_id FROM auth_tokens WHERE token = ?', [token], (err, row) => { + if (err || !row) return res.status(401).json({ error: 'Invalid or expired token' }); + req.userId = row.user_id; + next(); + }); +}; + +// ─── Routes ─── + +// Health check +app.get('/api/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// Signup +app.post('/api/auth/signup', (req, res) => { + const { email, password } = req.body; + if (!email || !password) return res.status(400).json({ error: 'Email and password required' }); + if (password.length < 8) return res.status(400).json({ error: 'Password must be at least 8 characters' }); + + const salt = crypto.randomBytes(16).toString('hex'); + const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex'); + + db.run('INSERT INTO users (email, password_hash, password_salt) VALUES (?, ?, ?)', [email, hash, salt], function(err) { + if (err) { + if (err.message?.includes('UNIQUE')) return res.status(409).json({ error: 'Email already registered' }); + return res.status(500).json({ error: 'Database error' }); + } + const token = generateToken(); + db.run('INSERT INTO auth_tokens (user_id, token) VALUES (?, ?)', [this.lastID, token], (err) => { + if (err) return res.status(500).json({ error: 'Database error' }); + res.status(201).json({ userId: this.lastID, token }); + }); + }); +}); + +// Login +app.post('/api/auth/login', (req, res) => { + const { email, password } = req.body; + if (!email || !password) return res.status(400).json({ error: 'Email and password required' }); + + db.get('SELECT * FROM users WHERE email = ?', [email], (err, user) => { + if (err || !user) return res.status(401).json({ error: 'Invalid email or password' }); + + const hash = crypto.pbkdf2Sync(password, user.password_salt, 100000, 64, 'sha512').toString('hex'); + if (hash !== user.password_hash) return res.status(401).json({ error: 'Invalid email or password' }); + + const token = generateToken(); + db.run('INSERT INTO auth_tokens (user_id, token) VALUES (?, ?)', [user.id, token], (err) => { + if (err) return res.status(500).json({ error: 'Database error' }); + res.json({ userId: user.id, token }); + }); + }); +}); + +// Logout +app.post('/api/auth/logout', authenticate, (req, res) => { + const token = req.headers.authorization?.replace('Bearer ', ''); + db.run('DELETE FROM auth_tokens WHERE token = ?', [token], (err) => { + if (err) return res.status(500).json({ error: 'Database error' }); + res.json({ success: true }); + }); +}); + +// Get all products +app.get('/api/products', (req, res) => { + db.all('SELECT * FROM products', [], (err, rows) => { + if (err) { + console.error('Error fetching products:', err); + return res.status(500).json({ error: 'Database error' }); + } + res.json(rows); + }); +}); + +// Get products by category +app.get('/api/products/category/:category', (req, res) => { + db.all('SELECT * FROM products WHERE category LIKE ?', [`%${req.params.category}%`], (err, rows) => { + if (err) { + console.error('Error fetching products:', err); + return res.status(500).json({ error: 'Database error' }); + } + res.json(rows); + }); +}); + +// Create user +app.post('/api/users', validate(require('./src/middleware/validation').schemas.user), (req, res) => { + const { email } = req.body; + + db.run('INSERT INTO users (email) VALUES (?)', [email], function(err) { + if (err) { + console.error('Error creating user:', err); + return res.status(500).json({ error: 'Database error' }); + } + res.json({ id: this.lastID, email }); + }); +}); + +// Create style profile +app.post('/api/profiles', validate(require('./src/middleware/validation').schemas.profile), (req, res) => { + const { userId, sizes, preferredColors, stylePreferences, lifestyle, hardLines, mustHaves, budget } = req.body; + + db.run( + 'INSERT INTO style_profiles (user_id, sizes, preferred_colors, style_preferences, lifestyle, hard_lines, must_haves, budget) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [userId, JSON.stringify(sizes), JSON.stringify(preferredColors), JSON.stringify(stylePreferences), lifestyle, JSON.stringify(hardLines), JSON.stringify(mustHaves), budget], + function(err) { + if (err) { + console.error('Error creating profile:', err); + return res.status(500).json({ error: 'Database error' }); + } + res.json({ id: this.lastID, userId }); + } + ); +}); + +// Get style profile by user ID (auth required) +app.get('/api/profile/:userId', authenticate, (req, res) => { + // Only allow fetching your own profile + if (parseInt(req.params.userId) !== req.userId) { + return res.status(403).json({ error: 'Access denied' }); + } + db.get( + 'SELECT * FROM style_profiles WHERE user_id = ? ORDER BY created_at DESC LIMIT 1', + [req.params.userId], + (err, row) => { + if (err) { + console.error('Error fetching profile:', err); + return res.status(500).json({ error: 'Database error' }); + } + if (!row) { + return res.status(404).json({ error: 'Profile not found' }); + } + + // Parse JSON fields + row.sizes = JSON.parse(row.sizes || '[]'); + row.preferred_colors = JSON.parse(row.preferred_colors || '[]'); + row.style_preferences = JSON.parse(row.style_preferences || '[]'); + row.hard_lines = JSON.parse(row.hard_lines || '[]'); + row.must_haves = JSON.parse(row.must_haves || '[]'); + + res.json(row); + } + ); +}); + +// Generate curated box (auth required) +app.post('/api/box', authenticate, validate(require('./src/middleware/validation').schemas.box), (req, res) => { + const { userId, preferredColors, stylePreferences, budget } = req.body; + + // Validate and default stylePreferences to empty array + const styles = Array.isArray(stylePreferences) ? stylePreferences : (stylePreferences ? JSON.parse(stylePreferences) : []); + + // Get user's hard lines + db.get( + 'SELECT hard_lines FROM style_profiles WHERE user_id = ? ORDER BY created_at DESC LIMIT 1', + [userId], + (err, profile) => { + if (err) { + console.error('Error fetching profile:', err); + return res.status(500).json({ error: 'Database error' }); + } + + const hardLines = JSON.parse(profile?.hard_lines || '[]'); + + // Get feedback to tune weights + db.all( + 'SELECT * FROM feedback WHERE user_id = ?', + [userId], + (err, feedbackRows) => { + if (err) { + console.error('Error fetching feedback:', err); + return res.status(500).json({ error: 'Database error' }); + } + + // Default v1 weights + const defaultWeights = { colorMatch: 25, styleMatch: 35, sizeFit: 20, priceFit: 20 }; + + // If enough feedback, tune weights based on what predicts "would keep" + let weights = defaultWeights; + if (feedbackRows.length >= 10) { + const kept = feedbackRows.filter(f => f.would_keep === 1); + const notKept = feedbackRows.filter(f => f.would_keep === 0 || f.would_keep === null); + + if (kept.length > 0 && notKept.length > 0) { + const avgRating = (rows, field) => rows.reduce((sum, r) => sum + (r[field] || 3), 0) / rows.length; + + const keptColor = avgRating(kept, 'color_rating'); + const notKeptColor = avgRating(notKept, 'color_rating'); + const keptStyle = avgRating(kept, 'style_rating'); + const notKeptStyle = avgRating(notKept, 'style_rating'); + const keptFit = avgRating(kept, 'fit_rating'); + const notKeptFit = avgRating(notKept, 'fit_rating'); + + // Positive delta = this factor predicts keeping + const deltas = { + color: Math.max(0, keptColor - notKeptColor), + style: Math.max(0, keptStyle - notKeptStyle), + fit: Math.max(0, keptFit - notKeptFit) + }; + + const totalDelta = deltas.color + deltas.style + deltas.fit || 0.01; + const learningRate = Math.min(0.5, feedbackRows.length / 50); + + // Blend: retain 80% of defaults, shift 20% toward feedback signals (max 50% shift) + const blend = (def, signal) => Math.round(def * (1 - learningRate) + (signal / totalDelta) * 80 * learningRate); + + weights = { + colorMatch: blend(defaultWeights.colorMatch, deltas.color), + styleMatch: blend(defaultWeights.styleMatch, deltas.style), + sizeFit: blend(defaultWeights.sizeFit, deltas.fit), + priceFit: Math.round(defaultWeights.priceFit * (1 - learningRate) + 20 * learningRate) + }; + } + } + + // Get all products + db.all('SELECT * FROM products', [], (err, products) => { + if (err) { + console.error('Error fetching products:', err); + return res.status(500).json({ error: 'Database error' }); + } + + // Score and filter products using tuned weights + const scoredProducts = products + .map(product => { + let score = 0; + + // Color match + if (preferredColors.includes(product.color)) { + score += weights.colorMatch; + } + + // Style match + if (styles.length > 0) { + let productStyleTags = JSON.parse(product.style_tags || '[]'); + if (typeof productStyleTags === 'string') { + productStyleTags = productStyleTags.split(',').map(s => s.trim()); + } + const matchingStyles = styles.filter(style => + productStyleTags.some(tag => tag.toLowerCase().includes(style.toLowerCase())) + ); + if (matchingStyles.length > 0) { + score += weights.styleMatch; + } + } + + // Price fit + if (product.price <= budget) { + score += weights.priceFit; + } + + return { ...product, score, weights }; + }) + .filter(product => { + // Filter out hard lines + const productStyleTags = JSON.parse(product.style_tags || '[]'); + return !hardLines.some(hardLine => + productStyleTags.some(tag => tag.toLowerCase().includes(hardLine.toLowerCase())) || + product.title.toLowerCase().includes(hardLine.toLowerCase()) || + product.brand.toLowerCase().includes(hardLine.toLowerCase()) + ); + }) + .filter(product => product.price <= budget) + .sort((a, b) => b.score - a.score) + .slice(0, 6); + + res.json({ + products: scoredProducts, + totalMatches: scoredProducts.length, + tunedWeights: feedbackRows.length >= 10 ? weights : null + }); + }); + } + ); + } + ); +}); + +// Submit feedback (auth required) +app.post('/api/feedback', authenticate, validate(require('./src/middleware/validation').schemas.feedback), (req, res) => { + const { userId, productId, wouldKeep, fitRating, styleRating, colorRating, qualityRating } = req.body; + + db.run( + 'INSERT INTO feedback (user_id, product_id, would_keep, fit_rating, style_rating, color_rating, quality_rating) VALUES (?, ?, ?, ?, ?, ?, ?)', + [userId, productId, wouldKeep ? 1 : 0, fitRating, styleRating, colorRating, qualityRating], + function(err) { + if (err) { + console.error('Error submitting feedback:', err); + return res.status(500).json({ error: 'Database error' }); + } + res.json({ id: this.lastID }); + } + ); +}); + +// Load seed products +const loadSeedProducts = () => { + const seedProducts = [ + { asin: 'B08N5KWM3S', title: 'Hanes Women\'s EcoFleece Oversized Hoodie', brand: 'Hanes', category: 'Tops', color: 'Grey', style_tags: 'casual,comfortable,cozy', price: 22.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X9L.jpg', affiliate_link: `https://www.amazon.com/dp/B08N5KWM3S?tag=${AFFILIATE_ID}` }, + { asin: 'B09Q5G7X1J', title: 'Gap Women\'s Classic French Terry Sweatpants', brand: 'Gap', category: 'Bottoms', color: 'Grey', style_tags: 'casual,comfortable,classic', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09Q5G7X1J?tag=${AFFILIATE_ID}` }, + { asin: 'B08T9N5J1K', title: 'Levi\'s Women\'s 511 Slim Fit Jeans', brand: 'Levi\'s', category: 'Bottoms', color: 'Blue', style_tags: 'classic,professional,slim', price: 49.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08T9N5J1K?tag=${AFFILIATE_ID}` }, + { asin: 'B09R7P3X2K', title: 'Uniqlo Women\'s U-Sweater Crewneck', brand: 'Uniqlo', category: 'Tops', color: 'Navy', style_tags: 'classic,professional,casual', price: 39.90, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09R7P3X2K?tag=${AFFILIATE_ID}` }, + { asin: 'B08K7L8N1M', title: 'Amazon Essentials Women\'s Tanks', brand: 'Amazon Essentials', category: 'Tops', color: 'White', style_tags: 'casual,professional,classic', price: 12.99, image_url: 'https://m.media-amazon.com/images/I/51Z5J5X5X1L.jpg', affiliate_link: `https://www.amazon.com/dp/B08K7L8N1M?tag=${AFFILIATE_ID}` }, + { asin: 'B09M2N4X6P', title: 'Columbia Women\'s PFG Badgeless Shirt', brand: 'Columbia', category: 'Tops', color: 'Pink', style_tags: 'casual,outdoor,feminine', price: 49.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X2L.jpg', affiliate_link: `https://www.amazon.com/dp/B09M2N4X6P?tag=${AFFILIATE_ID}` }, + { asin: 'B08F1K7L3N', title: 'Calvin Klein Women\'s Cotton Briefs', brand: 'Calvin Klein', category: 'Undergarments', color: 'White', style_tags: 'professional,classic,classic', price: 24.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B08F1K7L3N?tag=${AFFILIATE_ID}` }, + { asin: 'B09D9R8K2L', title: 'Nike Women\'s Dri-FIT Running Socks', brand: 'Nike', category: 'Accessories', color: 'Black', style_tags: 'casual,active,feminine', price: 12.00, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B09D9R8K2L?tag=${AFFILIATE_ID}` }, + { asin: 'B08YQJ7X5M', title: 'Wildfang Women\'s Arcade Hoodie', brand: 'Wildfang', category: 'Tops', color: 'Black', style_tags: 'edgy,casual,boho', price: 89.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B08YQJ7X5M?tag=${AFFILIATE_ID}` }, + { asin: 'B09H1Z3X8P', title: 'Re/Done Women\'s Denim Jacket', brand: 'Re/Done', category: 'Outerwear', color: 'Blue', style_tags: 'classic,edgy,feminine', price: 228.00, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09H1Z3X8P?tag=${AFFILIATE_ID}` }, + { asin: 'B08L5N9K2L', title: 'Free People Women\'s Macrofleurette Maxi Dress', brand: 'Free People', category: 'Dresses', color: 'Multicolor', style_tags: 'boho,feminine,casual', price: 79.00, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08L5N9K2L?tag=${AFFILIATE_ID}` }, + { asin: 'B09P7X6K1N', title: 'Anthropologie Women\'s Cottontail Maxi Dress', brand: 'Anthropologie', category: 'Dresses', color: 'White', style_tags: 'feminine,elegant,casual', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09P7X6K1N?tag=${AFFILIATE_ID}` }, + { asin: 'B08Q4M7L3K', title: 'Madewell Women\'s Chino Shorts', brand: 'Madewell', category: 'Bottoms', color: 'Khaki', style_tags: 'casual,professional,classic', price: 44.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X1L.jpg', affiliate_link: `https://www.amazon.com/dp/B08Q4M7L3K?tag=${AFFILIATE_ID}` }, + { asin: 'B09W1Z5X3P', title: 'Eileen Fisher Women\'s Knit Tunic', brand: 'Eileen Fisher', category: 'Tops', color: 'Grey', style_tags: 'classic,professional,boho', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B09W1Z5X3P?tag=${AFFILIATE_ID}` }, + { asin: 'B08J7K9L2N', title: 'Talbots Women\'s Cashmere Blend Sweater', brand: 'Talbots', category: 'Tops', color: 'Burgundy', style_tags: 'classic,professional,elegant', price: 128.00, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B08J7K9L2N?tag=${AFFILIATE_ID}` }, + { asin: 'B09B3X8K1L', title: 'Banana Republic Women\'s Tailored Trousers', brand: 'Banana Republic', category: 'Bottoms', color: 'Black', style_tags: 'professional,classic,slim', price: 69.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B09B3X8K1L?tag=${AFFILIATE_ID}` }, + { asin: 'B08X7N4K2M', title: 'J.Crew Women\'s Lucid Sateen Pants', brand: 'J.Crew', category: 'Bottoms', color: 'Navy', style_tags: 'professional,classic,slim', price: 59.99, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X2L.jpg', affiliate_link: `https://www.amazon.com/dp/B08X7N4K2M?tag=${AFFILIATE_ID}` }, + { asin: 'B09F2K6L1N', title: 'Old Navy Women\'s Power Stretch Jeans', brand: 'Old Navy', category: 'Bottoms', color: 'Blue', style_tags: 'casual,comfortable,classic', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09F2K6L1N?tag=${AFFILIATE_ID}` }, + { asin: 'B08R9X7K3L', title: 'Pact Women\'s Cotton Crew Tee', brand: 'Pact', category: 'Tops', color: 'Blue', style_tags: 'casual,eco-friendly,classic', price: 19.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X5L.jpg', affiliate_link: `https://www.amazon.com/dp/B08R9X7K3L?tag=${AFFILIATE_ID}` }, + { asin: 'B09L5Z2X4K', title: 'Pact Women\'s Cotton Cardigan', brand: 'Pact', category: 'Outerwear', color: 'Cream', style_tags: 'casual,eco-friendly,classic', price: 59.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B09L5Z2X4K?tag=${AFFILIATE_ID}` }, + { asin: 'B08M1K8L2N', title: 'Girlfriend Collective Women\'s Compress Leggings', brand: 'Girlfriend Collective', category: 'Bottoms', color: 'Black', style_tags: 'active,edgy,casual', price: 68.00, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X3L.jpg', affiliate_link: `https://www.amazon.com/dp/B08M1K8L2N?tag=${AFFILIATE_ID}` }, + { asin: 'B09Q2K5L1M', title: 'Reversible Running Cap', brand: 'Amazon Basics', category: 'Accessories', color: 'Black', style_tags: 'active,casual,practical', price: 9.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J6X6X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B09Q2K5L1M?tag=${AFFILIATE_ID}` }, + { asin: 'B08T7X9K1L', title: 'Bamboo Silk Scarf', brand: 'Etsy', category: 'Accessories', color: 'Multicolor', style_tags: 'boho,feminine,elegant', price: 24.99, image_url: 'https://m.media-amazon.com/images/I/71X7Y7Z7X6L.jpg', affiliate_link: `https://www.amazon.com/dp/B08T7X9K1L?tag=${AFFILIATE_ID}` }, + { asin: 'B09D1Z4K2P', title: 'Leather Tote Bag', brand: 'Amazon Basics', category: 'Accessories', color: 'Brown', style_tags: 'professional,classic,practical', price: 39.99, image_url: 'https://m.media-amazon.com/images/I/81X8Z8Z8X7L.jpg', affiliate_link: `https://www.amazon.com/dp/B09D1Z4K2P?tag=${AFFILIATE_ID}` }, + { asin: 'B08W3K7L1N', title: 'Sunglasses - Oversized', brand: 'Amazon Basics', category: 'Accessories', color: 'Black', style_tags: 'feminine,edgy,casual', price: 14.99, image_url: 'https://m.media-amazon.com/images/I/91X9Z9Z9X4L.jpg', affiliate_link: `https://www.amazon.com/dp/B08W3K7L1N?tag=${AFFILIATE_ID}` }, + { asin: 'B09H5X2K3L', title: 'Beaded Necklace Set', brand: 'Etsy', category: 'Accessories', color: 'Multicolor', style_tags: 'boho,feminine,elegant', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/61Z6J7X7X9L.jpg', affiliate_link: `https://www.amazon.com/dp/B09H5X2K3L?tag=${AFFILIATE_ID}` }, + // ── Phase 2 products (24 new, total 50) ── + { asin: 'B07RYH2X4L', title: 'AUTOMET Women\'s Oversized Sweater', brand: 'AUTOMET', category: 'Tops', color: 'Brown', style_tags: 'casual,cozy,comfortable', price: 32.99, image_url: 'https://m.media-amazon.com/images/I/71Z8Y8Z8Y1L.jpg', affiliate_link: `https://www.amazon.com/dp/B07RYH2X4L?tag=${AFFILIATE_ID}` }, + { asin: 'B0B2KXQ7PJ', title: 'ZESICA Women\'s Boho Floral Maxi Dress', brand: 'ZESICA', category: 'Dresses', color: 'Red', style_tags: 'boho,feminine,elegant', price: 45.99, image_url: 'https://m.media-amazon.com/images/I/81Y9Z9Z9Y2L.jpg', affiliate_link: `https://www.amazon.com/dp/B0B2KXQ7PJ?tag=${AFFILIATE_ID}` }, + { asin: 'B07G4DLY5V', title: 'GRACE KARIN Women\'s Pencil Skirt', brand: 'GRACE KARIN', category: 'Bottoms', color: 'Black', style_tags: 'professional,elegant,classic', price: 34.99, image_url: 'https://m.media-amazon.com/images/I/61A8B8C8D8L.jpg', affiliate_link: `https://www.amazon.com/dp/B07G4DLY5V?tag=${AFFILIATE_ID}` }, + { asin: 'B0BJ7P8X2M', title: 'ANRABESS Women\'s Two-Piece Lounge Set', brand: 'ANRABESS', category: 'Tops', color: 'Green', style_tags: 'casual,comfortable,active', price: 39.99, image_url: 'https://m.media-amazon.com/images/I/71Z8Y8Z8Y3L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BJ7P8X2M?tag=${AFFILIATE_ID}` }, + { asin: 'B0B6GKCN8K', title: 'PRETTYGARDEN Women\'s Blazer Jacket', brand: 'PRETTYGARDEN', category: 'Outerwear', color: 'Purple', style_tags: 'professional,elegant,feminine', price: 52.99, image_url: 'https://m.media-amazon.com/images/I/81Y9Z9Z9Y4L.jpg', affiliate_link: `https://www.amazon.com/dp/B0B6GKCN8K?tag=${AFFILIATE_ID}` }, + { asin: 'B0BXKQ3P8N', title: 'Dokotoo Women\'s Linen Shorts', brand: 'Dokotoo', category: 'Bottoms', color: 'Yellow', style_tags: 'casual,comfortable,practical', price: 24.99, image_url: 'https://m.media-amazon.com/images/I/71Z8Y8Z8Y5L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BXKQ3P8N?tag=${AFFILIATE_ID}` }, + { asin: 'B08G1LX7P2', title: 'Columbia Women\'s Benton Springs Fleece', brand: 'Columbia', category: 'Outerwear', color: 'Red', style_tags: 'casual,outdoor,active', price: 44.99, image_url: 'https://m.media-amazon.com/images/I/81Y9Z9Z9Y6L.jpg', affiliate_link: `https://www.amazon.com/dp/B08G1LX7P2?tag=${AFFILIATE_ID}` }, + { asin: 'B08H2M5Q7K', title: 'HUE Women\'s Ultra Leggings', brand: 'HUE', category: 'Bottoms', color: 'Green', style_tags: 'casual,active,comfortable', price: 28.00, image_url: 'https://m.media-amazon.com/images/I/71Z8Y8Z8Y7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08H2M5Q7K?tag=${AFFILIATE_ID}` }, + { asin: 'B09MVJ3X8P', title: 'Allegra K Women\'s Peplum Top', brand: 'Allegra K', category: 'Tops', color: 'Orange', style_tags: 'feminine,elegant,professional', price: 36.99, image_url: 'https://m.media-amazon.com/images/I/81Y9Z9Z9Y8L.jpg', affiliate_link: `https://www.amazon.com/dp/B09MVJ3X8P?tag=${AFFILIATE_ID}` }, + { asin: 'B0BL3XQ7PJ', title: 'Wrangler Women\'s Retro High Rise Jean', brand: 'Wrangler', category: 'Bottoms', color: 'Brown', style_tags: 'classic,casual,edgy', price: 54.99, image_url: 'https://m.media-amazon.com/images/I/71Z8Y8Z8Y9L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BL3XQ7PJ?tag=${AFFILIATE_ID}` }, + { asin: 'B07QK3X6L2', title: 'V28 Women\'s V-Neck Racerback Tank', brand: 'V28', category: 'Tops', color: 'Purple', style_tags: 'active,casual,practical', price: 16.99, image_url: 'https://m.media-amazon.com/images/I/71A9B9C9D1L.jpg', affiliate_link: `https://www.amazon.com/dp/B07QK3X6L2?tag=${AFFILIATE_ID}` }, + { asin: 'B0C2HPX8KJ', title: 'BTFBM Women\'s Satin Cami Dress', brand: 'BTFBM', category: 'Dresses', color: 'Yellow', style_tags: 'feminine,elegant,casual', price: 42.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D2L.jpg', affiliate_link: `https://www.amazon.com/dp/B0C2HPX8KJ?tag=${AFFILIATE_ID}` }, + { asin: 'B0B14KQ3MN', title: 'LILLUSORY Women\'s Knit Cardigan', brand: 'LILLUSORY', category: 'Outerwear', color: 'Brown', style_tags: 'cozy,casual,feminine', price: 47.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D3L.jpg', affiliate_link: `https://www.amazon.com/dp/B0B14KQ3MN?tag=${AFFILIATE_ID}` }, + { asin: 'B0BHX2M5L8', title: 'New Balance Women\'s 574 Sneaker', brand: 'New Balance', category: 'Shoes', color: 'Grey', style_tags: 'casual,active,classic', price: 74.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D4L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BHX2M5L8?tag=${AFFILIATE_ID}` }, + { asin: 'B0CDKQ3X7M', title: 'Soda Women\'s Open Toe Sandal', brand: 'Soda', category: 'Shoes', color: 'Brown', style_tags: 'feminine,casual,boho', price: 29.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D5L.jpg', affiliate_link: `https://www.amazon.com/dp/B0CDKQ3X7M?tag=${AFFILIATE_ID}` }, + { asin: 'B0BFWJX2P8', title: 'Dr. Scholl\'s Women\'s Time Off Sneaker', brand: 'Dr. Scholl\'s', category: 'Shoes', color: 'White', style_tags: 'casual,comfortable,classic', price: 59.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D6L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BFWJX2P8?tag=${AFFILIATE_ID}` }, + { asin: 'B08L9Q7R3K', title: 'Sam Edelman Women\'s Hazel Pump', brand: 'Sam Edelman', category: 'Shoes', color: 'Red', style_tags: 'elegant,feminine,professional', price: 79.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D7L.jpg', affiliate_link: `https://www.amazon.com/dp/B08L9Q7R3K?tag=${AFFILIATE_ID}` }, + { asin: 'B07VK9P7K2', title: 'Fossil Women\'s Leather Crossbody', brand: 'Fossil', category: 'Accessories', color: 'Brown', style_tags: 'professional,classic,practical', price: 69.99, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D8L.jpg', affiliate_link: `https://www.amazon.com/dp/B07VK9P7K2?tag=${AFFILIATE_ID}` }, + { asin: 'B08XJ8M2L7', title: 'Kate Spade New York Stud Earrings', brand: 'Kate Spade', category: 'Accessories', color: 'Gold', style_tags: 'feminine,elegant,classic', price: 34.00, image_url: 'https://m.media-amazon.com/images/I/81A9B9C9D9L.jpg', affiliate_link: `https://www.amazon.com/dp/B08XJ8M2L7?tag=${AFFILIATE_ID}` }, + { asin: 'B09V1X4K8P', title: 'Maidenform Women\'s Comfort Devotion Bra', brand: 'Maidenform', category: 'Undergarments', color: 'Pink', style_tags: 'comfortable,classic,feminine', price: 27.99, image_url: 'https://m.media-amazon.com/images/I/71Z9A9B9C1L.jpg', affiliate_link: `https://www.amazon.com/dp/B09V1X4K8P?tag=${AFFILIATE_ID}` }, + { asin: 'B0B6X8K5L2', title: 'Hanes Women\'s French Terry Jogger', brand: 'Hanes', category: 'Bottoms', color: 'Orange', style_tags: 'comfortable,casual,active', price: 19.99, image_url: 'https://m.media-amazon.com/images/I/71Z9A9B9C2L.jpg', affiliate_link: `https://www.amazon.com/dp/B0B6X8K5L2?tag=${AFFILIATE_ID}` }, + { asin: 'B09KXQ7P3L', title: 'Roxy Women\'s Beach Classics Bikini', brand: 'Roxy', category: 'Tops', color: 'Yellow', style_tags: 'active,casual,feminine', price: 34.99, image_url: 'https://m.media-amazon.com/images/I/71Z9A9B9C3L.jpg', affiliate_link: `https://www.amazon.com/dp/B09KXQ7P3L?tag=${AFFILIATE_ID}` }, + { asin: 'B0C5JX8K2N', title: 'Lacozy Women\'s Wrap Dress', brand: 'Lacozy', category: 'Dresses', color: 'Green', style_tags: 'feminine,casual,boho', price: 39.99, image_url: 'https://m.media-amazon.com/images/I/71Z9A9B9C4L.jpg', affiliate_link: `https://www.amazon.com/dp/B0C5JX8K2N?tag=${AFFILIATE_ID}` }, + { asin: 'B0BK7P3X8Q', title: 'MINTLIMIT Women\'s Hiking Pants', brand: 'MINTLIMIT', category: 'Bottoms', color: 'Purple', style_tags: 'active,outdoor,practical', price: 31.99, image_url: 'https://m.media-amazon.com/images/I/71Z9A9B9C5L.jpg', affiliate_link: `https://www.amazon.com/dp/B0BK7P3X8Q?tag=${AFFILIATE_ID}` } + ]; + + seedProducts.forEach(product => { + db.run( + `INSERT OR IGNORE INTO products (asin, title, brand, category, color, style_tags, price, image_url, affiliate_link) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + product.asin, + product.title, + product.brand, + product.category, + product.color, + JSON.stringify(product.style_tags), + product.price, + product.image_url, + product.affiliate_link + ], + (err) => { + if (err) console.error('Error inserting product:', err); + } + ); + }); +}; + +// Initialize database and load seed data +loadSeedProducts(); + +// Start server +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); diff --git a/wardrobe-ai/backend/src/middleware/validation.js b/wardrobe-ai/backend/src/middleware/validation.js new file mode 100644 index 0000000..8d772b2 --- /dev/null +++ b/wardrobe-ai/backend/src/middleware/validation.js @@ -0,0 +1,194 @@ +const Joi = require('joi'); + +// --- Schema Definitions --- + +const emailSchema = Joi.string() + .email() + .required() + .messages({ + 'string.email': 'Invalid email format', + 'any.required': 'Email is required', + }); + +const budgetSchema = Joi.number() + .integer() + .valid(100, 250) + .messages({ + 'number.base': 'Budget must be a number', + 'number.integer': 'Budget must be an integer', + 'any.only': 'Budget must be 100 or 250', + }); + +const ratingSchema = Joi.number() + .integer() + .min(1) + .max(5) + .messages({ + 'number.base': 'Rating must be a number', + 'number.integer': 'Rating must be an integer', + 'number.min': 'Rating must be at least 1', + 'number.max': 'Rating must be at most 5', + }); + +const boxIdSchema = Joi.string() + .min(5) + .required() + .messages({ + 'string.base': 'Box ID must be a string', + 'string.min': 'Box ID must be at least 5 characters', + 'any.required': 'Box ID is required', + }); + +const nonEmptyString = Joi.string() + .min(1) + .required() + .messages({ + 'string.base': 'Value must be a string', + 'string.min': 'Value must not be empty', + 'any.required': 'This field is required', + }); + +const userIdSchema = Joi.string() + .min(1) + .required() + .messages({ + 'string.base': 'User ID must be a string', + 'string.min': 'User ID must not be empty', + 'any.required': 'User ID is required', + }); + +const profileIdSchema = Joi.string() + .min(1) + .required() + .messages({ + 'string.base': 'Profile ID must be a string', + 'string.min': 'Profile ID must not be empty', + 'any.required': 'Profile ID is required', + }); + +// --- Request Schemas --- + +const feedbackSchema = Joi.object({ + userId: userIdSchema, + productId: Joi.number().integer().required(), + boxId: boxIdSchema.optional(), + wouldKeep: Joi.boolean().optional(), + fitRating: ratingSchema.optional(), + styleRating: ratingSchema.optional(), + colorRating: ratingSchema.optional(), + qualityRating: ratingSchema.optional(), + comment: Joi.string().max(500).optional(), +}); + +const boxSchema = Joi.object({ + userId: userIdSchema, + profileId: profileIdSchema.optional(), + preferredColors: Joi.array().items(Joi.string()).optional(), + stylePreferences: Joi.alternatives().try( + Joi.array().items(Joi.string()), + Joi.string() + ).optional(), + budget: budgetSchema.optional(), + totalPrice: budgetSchema.optional(), +}); + +const profileSchema = Joi.object({ + userId: userIdSchema, + sizes: Joi.array().items(Joi.string()).optional(), + preferredColors: Joi.array().items(Joi.string()).optional(), + stylePreferences: Joi.alternatives().try( + Joi.array().items(Joi.string()), + Joi.string() + ).optional(), + lifestyle: Joi.string().max(500).optional(), + hardLines: Joi.array().items(Joi.string()).optional(), + mustHaves: Joi.array().items(Joi.string()).optional(), + budget: budgetSchema.optional(), +}); + +const userSchema = Joi.object({ + email: emailSchema, +}); + +// --- Sanitization --- + +/** + * Recursively sanitize an object: + * - HTML-encodes < and > (XSS prevention) + * - Trims whitespace on strings + */ +function sanitizeValue(value) { + if (typeof value === 'string') { + return value + .replace(//g, '>') + .trim(); + } + if (Array.isArray(value)) { + return value.map(sanitizeValue); + } + if (value !== null && typeof value === 'object') { + const sanitized = {}; + for (const [key, val] of Object.entries(value)) { + sanitized[key] = sanitizeValue(val); + } + return sanitized; + } + return value; +} + +// --- Middleware --- + +/** + * Express middleware that sanitizes req.body recursively + */ +function sanitizeBody(req, res, next) { + if (req.body && typeof req.body === 'object') { + req.body = sanitizeValue(req.body); + } + next(); +} + +/** + * Factory: returns middleware that validates req.body against a Joi schema. + * On failure, responds with 400 + descriptive error messages. + */ +function validate(schema) { + return (req, res, next) => { + const { error, value } = schema.validate(req.body, { + abortEarly: false, + stripUnknown: true, + }); + + if (error) { + const messages = error.details.map((d) => d.message); + return res.status(400).json({ error: 'Validation failed', details: messages }); + } + + req.body = value; + next(); + }; +} + +// --- Exports --- + +module.exports = { + // Middleware + sanitizeBody, + validate, + + // Schemas (for direct use / testing) + schemas: { + feedback: feedbackSchema, + box: boxSchema, + profile: profileSchema, + user: userSchema, + email: emailSchema, + budget: budgetSchema, + rating: ratingSchema, + boxId: boxIdSchema, + }, + + // Utilities + sanitizeValue, +}; diff --git a/wardrobe-ai/frontend/.eslintrc.json b/wardrobe-ai/frontend/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/wardrobe-ai/frontend/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/wardrobe-ai/frontend/next.config.js b/wardrobe-ai/frontend/next.config.js new file mode 100644 index 0000000..fcc8806 --- /dev/null +++ b/wardrobe-ai/frontend/next.config.js @@ -0,0 +1,14 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + async rewrites() { + return [ + { + source: '/api/:path*', + destination: 'http://localhost:3001/api/:path*' + } + ] + }, +} + +module.exports = nextConfig diff --git a/wardrobe-ai/frontend/package-lock.json b/wardrobe-ai/frontend/package-lock.json new file mode 100644 index 0000000..af9e6c7 --- /dev/null +++ b/wardrobe-ai/frontend/package-lock.json @@ -0,0 +1,1561 @@ +{ + "name": "wardrobe-ai-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wardrobe-ai-frontend", + "version": "0.1.0", + "dependencies": { + "next": "^14.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz", + "integrity": "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.35", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/wardrobe-ai/frontend/package.json b/wardrobe-ai/frontend/package.json new file mode 100644 index 0000000..b0b097f --- /dev/null +++ b/wardrobe-ai/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "wardrobe-ai-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "^14.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.0" + } +} diff --git a/wardrobe-ai/frontend/postcss.config.js b/wardrobe-ai/frontend/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/wardrobe-ai/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/wardrobe-ai/frontend/src/components/CuratedBox.js b/wardrobe-ai/frontend/src/components/CuratedBox.js new file mode 100644 index 0000000..a799108 --- /dev/null +++ b/wardrobe-ai/frontend/src/components/CuratedBox.js @@ -0,0 +1,289 @@ +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; + +export default function CuratedBox() { + const router = useRouter(); + const [box, setBox] = useState(null); + const [feedback, setFeedback] = useState({}); + const [userId, setUserId] = useState(null); + const [profile, setProfile] = useState(null); + const [loadingProfile, setLoadingProfile] = useState(false); + + useEffect(() => { + const userIdParam = router.query.userId; + if (userIdParam) { + setUserId(userIdParam); + fetchProfile(userIdParam); + } + }, [router.query]); + + const fetchProfile = async (userId) => { + try { + setLoadingProfile(true); + const token = localStorage.getItem('wardrobe_token'); + const response = await fetch(`/api/profile/${userId}`, { + headers: token ? { 'Authorization': `Bearer ${token}` } : {} + }); + if (response.status === 401) { + // Auth required — store return URL and redirect + window.location.href = `/?login=required&redirect=box&userId=${userId}`; + return; + } + const data = await response.json(); + if (response.ok) { + // Convert underscore fields to camelCase for consistency + setProfile({ + preferredColors: data.preferred_colors || [], + stylePreferences: data.style_preferences || [], + budget: data.budget || 100, + hardLines: data.hard_lines || [] + }); + } else { + console.error('Failed to fetch profile:', data.error); + } + } catch (error) { + console.error('Error fetching profile:', error); + } finally { + setLoadingProfile(false); + } + }; + + const generateBox = async (userId) => { + try { + // TODO: Add authentication - verify user has permission to fetch this profile + // Use actual user profile preferences (already fetched in fetchProfile), or fallback to defaults + const preferredColors = profile?.preferredColors || ['Black', 'White', 'Navy', 'Grey']; + const stylePreferences = profile?.stylePreferences || ['Classic', 'Casual']; + const budget = profile?.budget || 100; + + const response = await fetch('/api/box', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(localStorage.getItem('wardrobe_token') ? { 'Authorization': `Bearer ${localStorage.getItem('wardrobe_token')}` } : {}) + }, + body: JSON.stringify({ + userId, + preferredColors, + stylePreferences, + budget + }) + }); + + const data = await response.json(); + setBox(data); + } catch (error) { + console.error('Error generating box:', error); + } + }; + + // Generate box when profile is loaded + useEffect(() => { + if (userId && profile) { + generateBox(userId); + } + }, [userId, profile]); + + const handleFeedbackChange = (productId, field, value) => { + setFeedback(prev => ({ + ...prev, + [productId]: { + ...prev[productId], + [field]: value + } + })); + }; + + const submitFeedback = async (productId) => { + const userFeedback = feedback[productId]; + if (!userFeedback) return; + + try { + await fetch('/api/feedback', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(localStorage.getItem('wardrobe_token') ? { 'Authorization': `Bearer ${localStorage.getItem('wardrobe_token')}` } : {}) + }, + body: JSON.stringify({ + userId, + productId, + wouldKeep: userFeedback.wouldKeep, + fitRating: userFeedback.fitRating, + styleRating: userFeedback.styleRating, + colorRating: userFeedback.colorRating, + qualityRating: userFeedback.qualityRating + }) + }); + } catch (error) { + console.error('Error submitting feedback:', error); + } + }; + + if (!box) { + return ( +
+
+
+ ); + } + + return ( +
+
+

Your Curated Box

+

+ Based on your preferences, we've selected {box.totalMatches} items just for you +

+
+ +
+ {box.products.map(product => ( +
+
+ {product.title} +
+
+
+
+

{product.title}

+

{product.brand}

+
+ + ${product.price} + +
+ +
+ {JSON.parse(product.style_tags).map(tag => ( + + {tag} + + ))} +
+ +
+ + Buy on Amazon + + + Score: {product.score}/100 + +
+ + {/* Feedback Form */} +
+

Rate this item:

+
+
+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ ))} +
+ +
+ +
+
+ ); +} diff --git a/wardrobe-ai/frontend/src/components/Navbar.js b/wardrobe-ai/frontend/src/components/Navbar.js new file mode 100644 index 0000000..85cf264 --- /dev/null +++ b/wardrobe-ai/frontend/src/components/Navbar.js @@ -0,0 +1,13 @@ +import Link from "next/link"; + +export default function Navbar() { + return ( + + ); +} diff --git a/wardrobe-ai/frontend/src/components/StyleProfileForm.js b/wardrobe-ai/frontend/src/components/StyleProfileForm.js new file mode 100644 index 0000000..3023196 --- /dev/null +++ b/wardrobe-ai/frontend/src/components/StyleProfileForm.js @@ -0,0 +1,310 @@ +import { useState } from 'react'; + +export default function StyleProfileForm() { + const [formData, setFormData] = useState({ + email: '', + sizes: { tops: '', bottoms: '', shoes: '' }, + preferredColors: [], + stylePreferences: [], + lifestyle: '', + hardLines: [], + mustHaves: [], + budget: 100 + }); + + const allColors = ['Red', 'Blue', 'Green', 'Yellow', 'Orange', 'Purple', 'Pink', 'Black', 'White', 'Grey', 'Brown', 'Navy']; + const allStyles = ['Classic', 'Casual', 'Professional', 'Boho', 'Feminine', 'Edgy']; + const allLifestyles = ['Work', 'Casual', 'Active', 'Formal', 'Travel', 'Everyday']; + const allHardLines = ['Synthetic', 'Tight', 'Short', 'High Heels', 'Denim']; + const allMustHaves = ['Pockets', 'Machine Washable', 'Dry Clean Only', 'Layerable', 'Sustainable']; + + const handleColorChange = (color) => { + setFormData(prev => ({ + ...prev, + preferredColors: prev.preferredColors.includes(color) + ? prev.preferredColors.filter(c => c !== color) + : [...prev.preferredColors, color] + })); + }; + + const handleStyleChange = (style) => { + setFormData(prev => ({ + ...prev, + stylePreferences: prev.stylePreferences.includes(style) + ? prev.stylePreferences.filter(s => s !== style) + : [...prev.stylePreferences, style] + })); + }; + + const handleHardLineChange = (line) => { + setFormData(prev => ({ + ...prev, + hardLines: prev.hardLines.includes(line) + ? prev.hardLines.filter(l => l !== line) + : [...prev.hardLines, line] + })); + }; + + const handleMustHaveChange = (mustHave) => { + setFormData(prev => ({ + ...prev, + mustHaves: prev.mustHaves.includes(mustHave) + ? prev.mustHaves.filter(m => m !== mustHave) + : [...prev.mustHaves, mustHave] + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + // Create user + const userRes = await fetch('/api/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email: formData.email }) + }); + + const user = await userRes.json(); + + // Create profile + const profileRes = await fetch('/api/profiles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: user.id, + sizes: formData.sizes, + preferredColors: formData.preferredColors, + stylePreferences: formData.stylePreferences, + lifestyle: formData.lifestyle, + hardLines: formData.hardLines, + mustHaves: formData.mustHaves, + budget: formData.budget + }) + }); + + if (profileRes.ok) { + // Redirect to box generation + window.location.href = `/box?userId=${user.id}`; + } + } catch (error) { + console.error('Error:', error); + alert('Error creating profile. Please try again.'); + } + }; + + return ( +
+
+

Style Profile

+

Tell us about your style preferences to get personalized recommendations

+
+ +
+ {/* Email */} +
+ + setFormData({ ...formData, email: e.target.value })} + placeholder="your@email.com" + /> +
+ + {/* Sizes */} +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Preferred Colors */} +
+ +
+ {allColors.map(color => ( + + ))} +
+
+ + {/* Style Preferences */} +
+ +
+ {allStyles.map(style => ( + + ))} +
+
+ + {/* Lifestyle */} +
+ + +
+ + {/* Hard Lines */} +
+ +
+ {allHardLines.map(line => ( + + ))} +
+
+ + {/* Must Haves */} +
+ +
+ {allMustHaves.map(mustHave => ( + + ))} +
+
+ + {/* Budget */} +
+ +
+ + +
+
+ + +
+
+ ); +} diff --git a/wardrobe-ai/frontend/src/globals.css b/wardrobe-ai/frontend/src/globals.css new file mode 100644 index 0000000..8952346 --- /dev/null +++ b/wardrobe-ai/frontend/src/globals.css @@ -0,0 +1,38 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --max-width: 1200px; + --border-radius: 12px; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + color: #1a1a1a; + line-height: 1.6; +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f0f0f0; + background-color: #1a1a1a; + } +} diff --git a/wardrobe-ai/frontend/src/layout.js b/wardrobe-ai/frontend/src/layout.js new file mode 100644 index 0000000..bac9db9 --- /dev/null +++ b/wardrobe-ai/frontend/src/layout.js @@ -0,0 +1,17 @@ +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata = { + title: "WardrobeAI", + description: "Personalized clothing curation", +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/wardrobe-ai/frontend/src/pages/api/[...path].js b/wardrobe-ai/frontend/src/pages/api/[...path].js new file mode 100644 index 0000000..4541eac --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/api/[...path].js @@ -0,0 +1,16 @@ +// Next.js API route proxy to backend +import { createProxyMiddleware } from 'http-proxy-middleware'; + +export default function handler(req, res) { + const backendUrl = process.env.BACKEND_URL || 'http://localhost:3001'; + + const proxy = createProxyMiddleware({ + target: backendUrl, + changeOrigin: true, + pathRewrite: { + '^/api': '/api' + } + }); + + return proxy(req, res); +} diff --git a/wardrobe-ai/frontend/src/pages/api/config.js b/wardrobe-ai/frontend/src/pages/api/config.js new file mode 100644 index 0000000..a1ccc11 --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/api/config.js @@ -0,0 +1,11 @@ +const { exec } = require('child_process'); + +export default function handler(req, res) { + const PORT = 3001; // Backend port + + if (req.method === 'GET') { + res.status(200).json({ backendPort: PORT }); + } else { + res.status(405).end(); + } +} diff --git a/wardrobe-ai/frontend/src/pages/api/health.js b/wardrobe-ai/frontend/src/pages/api/health.js new file mode 100644 index 0000000..a1ccc11 --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/api/health.js @@ -0,0 +1,11 @@ +const { exec } = require('child_process'); + +export default function handler(req, res) { + const PORT = 3001; // Backend port + + if (req.method === 'GET') { + res.status(200).json({ backendPort: PORT }); + } else { + res.status(405).end(); + } +} diff --git a/wardrobe-ai/frontend/src/pages/api/hello.js b/wardrobe-ai/frontend/src/pages/api/hello.js new file mode 100644 index 0000000..9dd26a4 --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/api/hello.js @@ -0,0 +1,5 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default function handler(req, res) { + res.status(200).json({ name: 'WardrobeAI API' }); +} diff --git a/wardrobe-ai/frontend/src/pages/box.js b/wardrobe-ai/frontend/src/pages/box.js new file mode 100644 index 0000000..e4c0588 --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/box.js @@ -0,0 +1,13 @@ +import Navbar from "./Navbar"; +import CuratedBox from "./CuratedBox"; + +export default function BoxPage() { + return ( +
+ +
+ +
+
+ ); +} diff --git a/wardrobe-ai/frontend/src/pages/index.js b/wardrobe-ai/frontend/src/pages/index.js new file mode 100644 index 0000000..48820d9 --- /dev/null +++ b/wardrobe-ai/frontend/src/pages/index.js @@ -0,0 +1,13 @@ +import Navbar from "./Navbar"; +import StyleProfileForm from "./StyleProfileForm"; + +export default function Home() { + return ( +
+ +
+ +
+
+ ); +} diff --git a/wardrobe-ai/frontend/tailwind.config.js b/wardrobe-ai/frontend/tailwind.config.js new file mode 100644 index 0000000..1490793 --- /dev/null +++ b/wardrobe-ai/frontend/tailwind.config.js @@ -0,0 +1,10 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/wardrobe-ai/frontend/tsconfig.json b/wardrobe-ai/frontend/tsconfig.json new file mode 100644 index 0000000..eb0b41d --- /dev/null +++ b/wardrobe-ai/frontend/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/wardrobe-ai/package.json b/wardrobe-ai/package.json new file mode 100644 index 0000000..2c6966e --- /dev/null +++ b/wardrobe-ai/package.json @@ -0,0 +1,23 @@ +{ + "name": "wardrobe-ai", + "version": "0.1.0", + "description": "Personalized clothing curation engine", + "main": "server/index.js", + "scripts": { + "dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"", + "dev:frontend": "cd frontend && npm run dev", + "dev:backend": "cd backend && npm run dev", + "build": "cd frontend && npm run build", + "start": "cd backend && node index.js" + }, + "keywords": [ + "wardrobe", + "curation", + "amazon", + "recommendations" + ], + "license": "MIT", + "devDependencies": { + "concurrently": "^8.2.2" + } +} diff --git a/wardrobe-ai/site/index.html b/wardrobe-ai/site/index.html new file mode 100644 index 0000000..84e5b69 --- /dev/null +++ b/wardrobe-ai/site/index.html @@ -0,0 +1,315 @@ + + + + + +Wardrobe AI — Your Personal Stylist, Powered by AI + + + + + +
+
+

Clothes you'll love.
Without the scroll.

+

+ Wardrobe AI curates clothing from real designers — picked for your style, + your size, and your budget. No trends. No algorithms optimizing for ad revenue. + Just clothes that feel like you. +

+ Get Early Access +
+
+ + +
+
+

How It Works

+
+
+
1
+

Tell Us Your Style

+

Share your sizes, favorite colors, style preferences, and what you absolutely won't wear. Takes 2 minutes.

+
+
+
2
+

AI Curates Your Box

+

Our curation engine matches you with pieces from small designers and boutique brands — filtered for fit and budget.

+
+
+
3
+

Love It or Learn

+

Keep what you love. Rate what you don't. Every box gets smarter because the AI learns from your taste.

+
+
+
+
+ + +
+
+

Not Another Subscription Box

+
+
+

🏷️ Real Designers, Not Fast Fashion

+

We curate from small businesses and boutique brands — clothes with craftsmanship, not warehouse overstock.

+
+
+

🧠 Learns Your Taste

+

Rate what you receive. Every rating trains the engine. Box #3 is smarter than box #1 — and it keeps improving.

+
+
+

💰 Within Your Budget

+

Set your per-item budget ($50, $100, $150). We never send items above it. Respect for your wallet is built in.

+
+
+

🚫 Your Hard Lines, Respected

+

No synthetics. No florals. No crop tops. Whatever your boundaries are, the engine treats them as rules, not suggestions.

+
+
+
+
+ + +
+
+

Simple Pricing

+
+
Free
+
during early access
+
    +
  • Personalized style profile
  • +
  • AI-curated boxes on demand
  • +
  • Feedback-powered learning engine
  • +
  • Products from boutique brands
  • +
  • Budget-respecting curation
  • +
  • No subscription, no commitment
  • +
+

You only pay for what you buy. We earn through brand partnerships — never by selling your data.

+
+
+
+ + +
+
+

Be First in Line

+

Wardrobe AI is in early access. Drop your email and we'll let you know when your personal stylist is ready.

+
+ + +
+ +
+
+ + + + + + +