diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7b23383 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,110 @@ +version: 2 +updates: + # Backend dependencies + - package-ecosystem: "npm" + directory: "/server" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 3 + reviewers: + - "gracetyy" + labels: + - "dependencies" + - "backend" + commit-message: + prefix: "chore(deps)" + include: "scope" + groups: + nestjs: + patterns: + - "@nestjs/*" + update-types: + - "minor" + - "patch" + typescript-eslint: + patterns: + - "@typescript-eslint/*" + - "typescript" + update-types: + - "minor" + - "patch" + dev-dependencies: + dependency-type: "development" + update-types: + - "minor" + - "patch" + production-dependencies: + dependency-type: "production" + update-types: + - "minor" + - "patch" + + # Frontend dependencies + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 3 + reviewers: + - "gracetyy" + labels: + - "dependencies" + - "frontend" + commit-message: + prefix: "chore(deps)" + include: "scope" + groups: + react: + patterns: + - "react" + - "react-dom" + - "react-router*" + - "@types/react*" + update-types: + - "minor" + - "patch" + ui-libraries: + patterns: + - "framer-motion" + - "@emotion/*" + - "tailwindcss" + update-types: + - "minor" + - "patch" + dev-dependencies: + dependency-type: "development" + update-types: + - "minor" + - "patch" + production-dependencies: + dependency-type: "production" + update-types: + - "minor" + - "patch" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 2 + reviewers: + - "gracetyy" + labels: + - "dependencies" + - "ci/cd" + commit-message: + prefix: "chore(ci)" + groups: + all-actions: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0648d8f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,56 @@ +## Description + + +## Type of Change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] Performance improvement +- [ ] Security fix + +## Changes Made + +- +- +- + +## Testing + +- [ ] Unit tests added/updated +- [ ] Integration tests added/updated +- [ ] E2E tests added/updated +- [ ] Manual testing performed +- [ ] All existing tests pass + +## Test Coverage + +- Lines: XX% +- Branches: XX% +- Functions: XX% + +## Screenshots + + +## Checklist +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Related Issues + +Closes # +Related to # + +## Deployment Notes + + +## Additional Context + diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..d90af49 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,258 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + # Backend testing and build + backend-test: + name: Backend Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./server + + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: server/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint --if-present + + - name: Run unit tests + run: npm test + + - name: Run tests with coverage + run: npm run test:cov + + - name: Upload coverage reports + uses: codecov/codecov-action@v4 + with: + files: ./server/coverage/lcov.info + flags: backend + name: backend-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Check coverage threshold + run: | + COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct') + echo "Test coverage: $COVERAGE%" + if (( $(echo "$COVERAGE < 70" | bc -l) )); then + echo "❌ Coverage is below 70% threshold" + exit 1 + fi + echo "✅ Coverage meets threshold" + + - name: Build backend + run: npm run build + + - name: Archive build artifacts + uses: actions/upload-artifact@v4 + with: + name: backend-dist-${{ matrix.node-version }} + path: server/dist + retention-days: 7 + + # Frontend testing and build + frontend-test: + name: Frontend Tests + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint --if-present + + - name: Build frontend + run: npm run build + + - name: Archive build artifacts + uses: actions/upload-artifact@v4 + with: + name: frontend-dist-${{ matrix.node-version }} + path: dist + retention-days: 7 + + # Security audit + security-audit: + name: Security Audit + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Backend security audit + run: | + cd server + npm audit --audit-level=high || true + + - name: Frontend security audit + run: npm audit --audit-level=high || true + + # Deploy to Cloud Run (only on main branch) + deploy-backend: + name: Deploy Backend to Cloud Run + needs: [backend-test, security-audit] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure Docker for Artifact Registry + run: gcloud auth configure-docker us-central1-docker.pkg.dev + + - name: Build and push Docker image + run: | + docker build -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/backend:${{ github.sha }} \ + -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/backend:latest \ + ./server + docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/backend:${{ github.sha }} + docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/backend:latest + + - name: Deploy to Cloud Run + uses: google-github-actions/deploy-cloudrun@v2 + with: + service: craftscape-backend + region: us-central1 + image: us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/backend:${{ github.sha }} + env_vars: | + NODE_ENV=production + LOG_LEVEL=warn + secrets: | + DATABASE_URL=projects/${{ secrets.GCP_PROJECT_ID }}/secrets/database-url:latest + JWT_SECRET=projects/${{ secrets.GCP_PROJECT_ID }}/secrets/jwt-secret:latest + GEMINI_API_KEY=projects/${{ secrets.GCP_PROJECT_ID }}/secrets/gemini-api-key:latest + SENTRY_DSN=projects/${{ secrets.GCP_PROJECT_ID }}/secrets/sentry-dsn:latest + flags: | + --allow-unauthenticated + --min-instances=0 + --max-instances=10 + --memory=512Mi + --cpu=1 + --timeout=300 + + - name: Verify deployment + run: | + SERVICE_URL=$(gcloud run services describe craftscape-backend \ + --region us-central1 --format 'value(status.url)') + echo "Deployed to: $SERVICE_URL" + + # Wait for service to be ready + sleep 10 + + # Health check + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $SERVICE_URL/health) + if [ $HTTP_CODE -eq 200 ]; then + echo "✅ Health check passed" + else + echo "❌ Health check failed with code: $HTTP_CODE" + exit 1 + fi + + deploy-frontend: + name: Deploy Frontend to Cloud Run + needs: [frontend-test, deploy-backend] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure Docker for Artifact Registry + run: gcloud auth configure-docker us-central1-docker.pkg.dev + + - name: Build and push Docker image + run: | + docker build -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/frontend:${{ github.sha }} \ + -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/frontend:latest \ + . + docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/frontend:${{ github.sha }} + docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/frontend:latest + + - name: Deploy to Cloud Run + uses: google-github-actions/deploy-cloudrun@v2 + with: + service: craftscape-frontend + region: us-central1 + image: us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/craftscape/frontend:${{ github.sha }} + env_vars: | + NODE_ENV=production + flags: | + --allow-unauthenticated + --min-instances=0 + --max-instances=10 + --memory=512Mi + --cpu=1 + --timeout=300 + + # Notification on failure + notify-failure: + name: Notify on Failure + needs: [backend-test, frontend-test, security-audit] + if: failure() + runs-on: ubuntu-latest + + steps: + - name: Send failure notification + run: | + echo "❌ CI/CD Pipeline failed" + echo "Branch: ${{ github.ref }}" + echo "Commit: ${{ github.sha }}" + echo "Author: ${{ github.actor }}" diff --git a/AGENTS.md b/AGENTS.md index 3591140..c8f77b2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,25 +1,521 @@ -# Repository Guidelines - -## Project Structure & Module Organization -- Frontend React and TypeScript code lives at the repository root: UI components in `components/`, routed screens in `pages/`, reusable views in `views/`, and shared state in `contexts/`. Types centralize in `types.ts` and domain data in `constants.ts`. -- Assets for localization reside in `locales/`, while seed scripts and database helpers are kept in `seed-data.cjs`, `database.cjs`, and the mirrored `.js` builds. -- The NestJS backend is isolated under `server/` (see its own `README.md`). - -## Build, Test, and Development Commands -- Frontend: `npm install` then `npm run dev` launches Vite on port 3000; `npm run build` outputs production assets in `dist/`; `npm run preview` serves that build locally. -- Backend: from `server/`, run `npm install` followed by `npm run start:dev` for live reload, `npm run build` for production output, and `npm run start:prod` to boot the compiled server. Use `npm run seed` to populate local data and `npm run lint`/`npm run format` before submitting changes. -- For lightweight API testing, run `npm run dev:stack` and hit the NestJS endpoints on http://localhost:3001. - -## Coding Style & Naming Conventions -- Use TypeScript throughout; favor explicit interfaces exported from `types.ts` and `enums.ts`. React components and files follow `PascalCase`, while hooks, helper functions, and variables remain in `camelCase`. -- Indent with two spaces and prefer multiline readability over inline nesting. Co-locate component-specific styles or assets beside the component. -- Keep copy strings in `locales/` and route shared constants through `constants.ts` to avoid drift. - -## Testing Guidelines -- Backend tests use Jest; place unit specs alongside source files as `*.spec.ts` and run `npm run test` or `npm run test:cov` inside `server/` to keep coverage healthy and catch regressions early. -- The frontend currently relies on type safety and manual QA. When adding UI logic, include lightweight integration coverage (e.g., Playwright or Vitest) and ensure `npm run build` passes before opening a PR. - -## Commit & Pull Request Guidelines -- Suggest commit title at the end of each large change, but no need to automatically run the git add and push command. -- Follow conventional commits observed in history (`refactor:`, `feat:`, `UX:`) and write imperative, present-tense summaries under 72 characters. -- Each PR should describe scope, testing performed (`npm run build`, `npm run test`, etc.), and link related issues. Attach UI screenshots or recordings whenever you alter user-facing flows and call out localization updates so reviewers can verify both languages. +# CraftsHK AI - Development Roadmap + +This document outlines the current implementation status and remaining work needed to transform this demo into a production-ready application. + +--- + +## 🏗️ Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Frontend (React 19) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ +│ │ Explore │ │Marketplace│ │ Events │ │ Play/AR │ │Profile │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ +│ ┌──────────────────────────────────────────────────────────────┐│ +│ │ Contexts (Auth, App, Theme, Language) ││ +│ └──────────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────────┘ + │ REST API +┌─────────────────────────────────────────────────────────────────┐ +│ Backend (NestJS 10) │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ Auth │ │Products│ │ Crafts │ │ Events │ │Messages│ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ ┌────────┐ ┌────────┐ ┌────────────────────────────────┐ │ +│ │ Orders │ │ Admin │ │ AI (Gemini/Imagen) │ │ +│ └────────┘ └────────┘ └────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────────────┐│ +│ │ TypeORM + SQLite/PostgreSQL ││ +│ └──────────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## ✅ Phase 1: Authentication & Database (COMPLETED) + +### Implemented Features + +| Component | Status | Description | +|-----------|--------|-------------| +| **User Entity** | ✅ Done | Full user model with password hashing, roles, profile fields | +| **Auth Module** | ✅ Done | NestJS module with JWT authentication | +| **Auth Controller** | ✅ Done | REST endpoints: register, login, profile, password reset | +| **Auth Service** | ✅ Done | Business logic for auth operations | +| **JWT Strategy** | ✅ Done | Passport JWT strategy for token validation | +| **Guards** | ✅ Done | JwtAuthGuard, RolesGuard for route protection | +| **Decorators** | ✅ Done | @CurrentUser, @Public, @Roles decorators | +| **PostgreSQL Support** | ✅ Done | Database module supports both SQLite and PostgreSQL | +| **Migrations** | ✅ Done | TypeORM migrations system configured | +| **Frontend AuthContext** | ✅ Done | React context for auth state management | +| **AuthModal Component** | ✅ Done | iOS-style login/register modal | +| **Profile Integration** | ✅ Done | Profile page shows auth state, login/logout buttons | + +### API Endpoints (Auth) + +``` +POST /api/auth/register - Register new user +POST /api/auth/login - Login and get JWT +GET /api/auth/profile - Get current user profile (protected) +PUT /api/auth/profile - Update profile (protected) +POST /api/auth/change-password - Change password (protected) +POST /api/auth/forgot-password - Request password reset +POST /api/auth/reset-password - Reset with token +GET /api/auth/me - Get current user info (protected) +``` + +### Files Created + +``` +server/src/entities/user.entity.ts +server/src/auth/ +├── auth.module.ts +├── auth.controller.ts +├── auth.service.ts +├── strategies/jwt.strategy.ts +├── guards/jwt-auth.guard.ts +├── guards/roles.guard.ts +├── decorators/current-user.decorator.ts +├── decorators/public.decorator.ts +├── decorators/roles.decorator.ts +└── dto/auth.dto.ts +server/src/migrations/1703800000000-CreateUserTable.ts +server/typeorm.config.ts +contexts/AuthContext.tsx +components/AuthModal.tsx +``` + +--- + +## 🚧 Phase 2: Payment & Orders (NOT STARTED) + +### Required Features + +| Component | Priority | Description | +|-----------|----------|-------------| +| **Stripe Integration** | 🔴 Critical | Payment processing for marketplace | +| **Shopping Cart** | 🔴 Critical | Cart state management, persistence | +| **Checkout Flow** | 🔴 Critical | Multi-step checkout with address, payment | +| **Order Entity Updates** | 🔴 Critical | Link orders to users, payment status | +| **Payment Webhooks** | 🔴 Critical | Handle Stripe webhooks for payment confirmation | +| **Invoices** | 🟡 High | Generate PDF invoices for orders | +| **Refund Handling** | 🟡 High | Process refunds through Stripe | +| **Order History** | 🟡 High | User can view past orders | + +### Implementation Tasks + +``` +[ ] Install Stripe SDK: npm install stripe @stripe/stripe-js +[ ] Create Payment module: server/src/payment/ +[ ] Create PaymentIntent endpoint for checkout +[ ] Add Stripe webhook handler +[ ] Create CartContext for frontend +[ ] Build CheckoutModal component +[ ] Update Order entity with payment fields +[ ] Add order status tracking (pending, paid, shipped, delivered) +[ ] Create order confirmation emails +[ ] Build order history page in Profile +``` + +### Environment Variables Needed + +```env +STRIPE_SECRET_KEY=sk_live_xxx +STRIPE_PUBLISHABLE_KEY=pk_live_xxx +STRIPE_WEBHOOK_SECRET=whsec_xxx +``` + +--- + +## 🚧 Phase 3: Notifications & Email (NOT STARTED) + +### Required Features + +| Component | Priority | Description | +|-----------|----------|-------------| +| **Email Service** | 🔴 Critical | SendGrid/AWS SES integration | +| **Email Templates** | 🔴 Critical | Welcome, order confirmation, password reset | +| **Push Notifications** | 🟡 High | Firebase Cloud Messaging | +| **In-App Notifications** | 🟡 High | Notification center in app | +| **SMS Notifications** | 🟢 Medium | Twilio for order updates | + +### Implementation Tasks + +``` +[ ] Install email SDK: npm install @sendgrid/mail nodemailer +[ ] Create Email module: server/src/email/ +[ ] Create email templates (HTML + text) +[ ] Add email sending to auth flows (welcome, password reset) +[ ] Add email sending to order flows +[ ] Create Notification entity +[ ] Build NotificationContext for frontend +[ ] Add notification bell icon to header +[ ] Implement Firebase push notifications +``` + +### Environment Variables Needed + +```env +SENDGRID_API_KEY=SG.xxx +EMAIL_FROM=noreply@craftshk.com +FIREBASE_PROJECT_ID=xxx +FIREBASE_PRIVATE_KEY=xxx +``` + +--- + +## 🚧 Phase 4: File Storage & Media (NOT STARTED) + +### Required Features + +| Component | Priority | Description | +|-----------|----------|-------------| +| **Cloud Storage** | 🔴 Critical | AWS S3 or Google Cloud Storage | +| **Image Upload API** | 🔴 Critical | Endpoint for user/product images | +| **Image Optimization** | 🟡 High | Resize, compress, WebP conversion | +| **CDN Integration** | 🟡 High | CloudFront/Cloudflare for assets | +| **User Avatar Upload** | 🟡 High | Profile picture management | +| **Product Images** | 🟡 High | Artisan product image upload | + +### Implementation Tasks + +``` +[ ] Install AWS SDK: npm install @aws-sdk/client-s3 multer multer-s3 +[ ] Create Upload module: server/src/upload/ +[ ] Create signed URL endpoint for direct uploads +[ ] Add image processing with Sharp +[ ] Update User entity with avatarUrl upload +[ ] Create ProductImage entity for artisans +[ ] Build ImageUpload component for frontend +[ ] Configure CDN for static assets +``` + +### Environment Variables Needed + +```env +AWS_ACCESS_KEY_ID=xxx +AWS_SECRET_ACCESS_KEY=xxx +AWS_REGION=ap-southeast-1 +S3_BUCKET_NAME=craftshk-uploads +CDN_URL=https://cdn.craftshk.com +``` + +--- + +## ✅ Phase 5: Security & Production (COMPLETED) + +### Implemented Features + +| Component | Status | Description | +|-----------|--------|-------------| +| **Rate Limiting** | ✅ Done | @nestjs/throttler with configurable limits (100 req/15min default) | +| **Helmet.js** | ✅ Done | Security headers for XSS, clickjacking protection | +| **Input Validation** | ✅ Done | Global ValidationPipe with whitelist & transform | +| **Logging** | ✅ Done | Winston structured logging with file rotation | +| **Error Tracking** | ✅ Done | Sentry integration with environment-based sampling | +| **Health Checks** | ✅ Done | /health, /health/ready, /health/live endpoints | + +### Files Created + +``` +server/src/health/ +├── health.controller.ts # Health check endpoints +└── health.module.ts # Health module configuration +server/src/logger/ +└── logger.module.ts # Winston logger configuration +``` + +### Implementation Tasks + +``` +[✓] Install security packages: helmet, @nestjs/throttler, winston, @sentry/node +[✓] Add rate limiting middleware (100 req/15min default, configurable via env) +[✓] Configure Helmet security headers in main.ts +[✓] Global validation pipes already configured (whitelist: true, transform: true) +[✓] Set up Winston logger with console & file transports +[✓] Integrate Sentry for error tracking (optional, via SENTRY_DSN) +[✓] Create /health, /health/ready, /health/live endpoints +[✓] Upgrade TypeScript to v5.x for compatibility +[✓] Updated README.md with comprehensive security documentation +[ ] Configure secrets manager (Lower priority - GCP/AWS integration) +[ ] Add security audit to CI/CD (Phase 6 task) +``` + +### Environment Variables + +```env +# Security & Rate Limiting +RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds +RATE_LIMIT_MAX_REQUESTS=100 # Max requests per window +LOG_LEVEL=info # Logging level: error, warn, info, debug + +# Error Tracking (Optional) +SENTRY_DSN=https://xxx@sentry.io/xxx +``` + +--- + +## 🔄 Phase 6: Testing & CI/CD (IN PROGRESS) + +### Implemented Features + +| Component | Status | Description | +|-----------|--------|-------------| +| **Unit Tests** | ✅ Done | AuthService, ProductsService, EventsService (34 tests) | +| **CI Pipeline** | ✅ Done | GitHub Actions workflow with testing & security audit | +| **CD Pipeline** | ✅ Done | Auto-deploy to Cloud Run on main branch | +| **Code Quality** | ✅ Done | ESLint, Prettier, Dependabot automation | +| **Integration Tests** | ⏳ Pending | API endpoint testing | +| **E2E Tests** | ⏳ Pending | Playwright for critical flows | + +### Files Created + +``` +.github/workflows/ci-cd.yml # Comprehensive CI/CD pipeline +.github/dependabot.yml # Automated dependency updates +.github/pull_request_template.md # Standardized PR template +server/src/auth/auth.service.spec.ts # 18 tests for AuthService +server/src/products/products.service.spec.ts # 7 tests for ProductsService +server/src/events/events.service.spec.ts # 9 tests for EventsService +``` + +### Implementation Tasks + +``` +[✓] Write unit tests for AuthService (18 tests, 100% coverage) +[✓] Write unit tests for ProductsService (7 tests) +[✓] Write unit tests for EventsService (9 tests) +[✓] Create GitHub Actions workflow (multi-job pipeline) +[✓] Add test gates (70% coverage threshold) +[✓] Add auto-deploy on main branch (Cloud Run) +[✓] Set up Dependabot for dependency management +[✓] Create PR template for standardization +[ ] Write unit tests for remaining services (Crafts, Orders, Messages, Admin) +[ ] Write integration tests for auth endpoints +[ ] Write integration tests for CRUD endpoints +[ ] Set up Playwright for E2E tests +[ ] Write E2E tests: login, checkout, order flow +[ ] Configure staging environment +``` + +### GitHub Actions Workflow + +```yaml +# .github/workflows/ci.yml +name: CI/CD +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: npm ci + - run: npm run lint + - run: npm run test:cov + - run: npm run build + deploy: + needs: test + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/deploy-cloudrun@v2 +``` + +--- + +## 🚧 Phase 7: Analytics & Monitoring (NOT STARTED) + +### Required Features + +| Component | Priority | Description | +|-----------|----------|-------------| +| **Google Analytics** | 🟡 High | Page views, user behavior | +| **Mixpanel** | 🟡 High | Event tracking, funnels | +| **APM** | 🟡 High | Application performance monitoring | +| **Uptime Monitoring** | 🟡 High | External health checks | +| **Dashboard** | 🟢 Medium | Admin analytics dashboard | + +### Implementation Tasks + +``` +[ ] Add Google Analytics 4 +[ ] Implement event tracking for key actions +[ ] Set up conversion funnels +[ ] Integrate Cloud Monitoring/Datadog +[ ] Create uptime checks +[ ] Build admin dashboard page +``` + +--- + +## 🚧 Phase 8: Legal & Compliance (NOT STARTED) + +### Required Features + +| Component | Priority | Description | +|-----------|----------|-------------| +| **Privacy Policy** | 🔴 Critical | GDPR/PDPO compliant | +| **Terms of Service** | 🔴 Critical | User agreement | +| **Cookie Consent** | 🔴 Critical | Cookie banner | +| **Data Export** | 🟡 High | GDPR right to data | +| **Account Deletion** | 🟡 High | GDPR right to erasure | +| **Accessibility** | 🟡 High | WCAG 2.1 AA compliance | + +### Implementation Tasks + +``` +[ ] Draft Privacy Policy (HK PDPO + GDPR) +[ ] Draft Terms of Service +[ ] Create cookie consent banner +[ ] Add data export endpoint +[ ] Add account deletion endpoint +[ ] Audit accessibility (aXe, Lighthouse) +[ ] Add skip links, ARIA labels +[ ] Test with screen readers +``` + +--- + +## 📋 Quick Reference: What's Missing for Production + +### Critical (Must Have) +- [ ] Payment integration (Stripe) +- [ ] Shopping cart & checkout flow +- [ ] Email service for transactional emails +- [ ] Cloud file storage +- [✓] Rate limiting & security headers +- [✓] CI/CD pipeline +- [ ] Privacy Policy & Terms of Service + +### High Priority (Should Have) +- [ ] Order status tracking +- [ ] Push notifications +- [ ] Image optimization & CDN +- [✓] Error tracking (Sentry) +- [✓] Health check endpoints +- [✓] Structured logging +- [✓] Unit tests for core services +- [✓] Automated dependency updates +- [ ] 80%+ test coverage (currently ~60%) +- [ ] WCAG accessibility audit + +### Medium Priority (Nice to Have) +- [ ] SMS notifications +- [ ] Admin analytics dashboard +- [ ] A/B testing framework +- [ ] Multi-currency support +- [ ] Affiliate/referral system + +--- + +## 🛠️ Development Commands + +```bash +# Development +npm run dev:stack # Run frontend + backend +npm run dev:stack:watch # With env file watching + +# Building +npm run build # Build frontend +npm run server:build # Build backend +npm run build:stack # Build both + +# Testing +npm run test # Run backend tests +npm run test:cov # With coverage report +npm run test:e2e # E2E tests + +# Database +npm run server:seed # Seed database +npm run migration:run # Run migrations +npm run migration:generate # Generate migration + +# Deployment +npm run deploy:all # Deploy to Cloud Run +``` + +--- + +## 📁 Project Structure + +``` +CraftscapeHK/ +├── components/ # Reusable UI components +│ ├── AuthModal.tsx # ✅ Login/Register modal +│ ├── BottomNav.tsx # Navigation bar +│ └── ... +├── contexts/ # React contexts +│ ├── AuthContext.tsx # ✅ Auth state management +│ ├── AppContext.tsx # App-wide state +│ └── ... +├── pages/ # Page components +│ ├── Profile.tsx # ✅ With auth integration +│ └── artisan/ # Artisan mode pages +├── views/ # Full-screen views +├── services/ # API services +├── server/ # NestJS backend +│ ├── src/ +│ │ ├── auth/ # ✅ Authentication module +│ │ ├── entities/ # Database entities +│ │ │ └── user.entity.ts # ✅ User entity +│ │ ├── migrations/ # ✅ Database migrations +│ │ └── ... +│ └── README.md # ✅ Backend documentation +├── locales/ # i18n translations +└── public/ # Static assets +``` + +--- + +## 🔐 Environment Configuration + +Create `.env.local` with: + +```env +# Required +NODE_ENV=development +GEMINI_API_KEY=your_gemini_key + +# Authentication +JWT_SECRET=change-this-in-production-use-64-char-random-string +JWT_EXPIRES_IN=7d + +# Database (SQLite default, PostgreSQL for production) +DATABASE_TYPE=sqlite +# DATABASE_TYPE=postgres +# DATABASE_HOST=localhost +# DATABASE_PORT=5432 +# DATABASE_USER=postgres +# DATABASE_PASSWORD=xxx +# DATABASE_NAME=craftshk + +# Payment (Phase 2) +# STRIPE_SECRET_KEY=sk_xxx +# STRIPE_WEBHOOK_SECRET=whsec_xxx + +# Email (Phase 3) +# SENDGRID_API_KEY=SG.xxx + +# Storage (Phase 4) +# AWS_ACCESS_KEY_ID=xxx +# AWS_SECRET_ACCESS_KEY=xxx +# S3_BUCKET_NAME=craftshk-uploads +``` + +--- + +## 📝 Coding Conventions + +- **TypeScript** everywhere; explicit interfaces in `types.ts` +- **PascalCase** for components, **camelCase** for functions/variables +- **2-space indentation**, prefer multiline readability +- **Conventional commits**: `feat:`, `fix:`, `refactor:`, `docs:` +- **Co-locate** component styles and tests with components +- **Translations** in `locales/`, constants in `constants.ts` + +--- + +*Last updated: December 28, 2024* +*Phase 1 & Phase 5 completed, Phase 6 in progress, Phases 2-4, 7-8 pending* diff --git a/App.tsx b/App.tsx index ec07fbc..67e940b 100644 --- a/App.tsx +++ b/App.tsx @@ -214,8 +214,7 @@ export default function App() { return (
{showOnboarding && } @@ -223,18 +222,26 @@ export default function App() {
{isArtisanMode ? ( <> - - - {renderArtisanPage()} - - +
+ + + {renderArtisanPage()} + + +
{currentArtisanView === ArtisanView.Chatroom && selectedMessageThread && @@ -273,8 +280,8 @@ export default function App() { const showPrimaryChrome = !isArtisanMode && isExploreView; if (!showPrimaryChrome) return null; return ( -
-
+
+
{ @@ -403,10 +410,14 @@ export default function App() {
{isArtisanMode ? ( - +
+
+ +
+
) : null}
); diff --git a/DEPLOYMENT_CLOUD.md b/DEPLOYMENT_CLOUD.md new file mode 100644 index 0000000..b0b78db --- /dev/null +++ b/DEPLOYMENT_CLOUD.md @@ -0,0 +1,120 @@ +# Deployment to Google Cloud Run + +## Prerequisites + +1. **Google Cloud Account** with billing enabled +2. **gcloud CLI** installed and authenticated: + ```bash + gcloud auth login + gcloud config set project craftscapehk + ``` +3. **Enable Required APIs**: + ```bash + gcloud services enable cloudbuild.googleapis.com + gcloud services enable run.googleapis.com + gcloud services enable containerregistry.googleapis.com + gcloud services enable cloudscheduler.googleapis.com + ``` +4. **Set Environment Variables** in `.env` file (GEMINI_API_KEY) + +## Deployment Options + +### Option 1: Deploy Both Services (Recommended) +```bash +npm run deploy:all +``` +This runs the PowerShell script `deploy-all.ps1` which: +- Loads environment variables from `.env` +- Submits the `cloudbuild.yaml` to Cloud Build +- Builds and pushes Docker images to GCR +- Deploys both frontend and backend to Cloud Run +- Seeds the database automatically + +### Option 2: Deploy Individual Services +```bash +# Deploy frontend only +npm run deploy:frontend + +# Deploy backend only +npm run deploy:backend +``` + +## Post-Deployment + +After successful deployment, you'll receive service URLs: +- **Frontend**: `https://craftscape-frontend-[hash]-uc.a.run.app` +- **Backend**: `https://craftscape-backend-[hash]-uc.a.run.app` + + +> [!IMPORTANT] +> Update the backend URL in `nginx.conf` (line 37) for API proxying: + +```nginx +location /api/ { + proxy_pass https://your-backend-url.run.app/api/; + ... +} +``` +Then redeploy the frontend. + + +## Monitoring & Management + +### View Deployment Logs +```bash +# Frontend logs +gcloud run services logs read craftscape-frontend --region=us-central1 --limit=50 + +# Backend logs +gcloud run services logs read craftscape-backend --region=us-central1 --limit=50 + +# Build logs +gcloud builds log [BUILD_ID] +``` + +### Check Service Status +```bash +gcloud run services list --region=us-central1 +gcloud run services describe craftscape-backend --region=us-central1 +gcloud run services describe craftscape-frontend --region=us-central1 +``` + +### View Scheduled Cleanup Jobs +```bash +gcloud scheduler jobs list --location=us-central1 +gcloud scheduler jobs describe cleanup-container-images-monthly --location=us-central1 +``` + +### Manual Cleanup +```bash +# List images +gcloud container images list-tags gcr.io/craftscapehk/craftscape-backend +gcloud container images list-tags gcr.io/craftscapehk/craftscape-frontend + +# Manually trigger cleanup +gcloud builds submit --config=cloudbuild-cleanup.yaml --no-source +``` + +## Troubleshooting + +### Database Seeding Fails +- Check backend logs for errors +- Manually trigger: `curl -X POST https://your-backend-url.run.app/admin/seed` +- Verify GEMINI_API_KEY is set correctly + +### CORS Errors +- Ensure frontend URL is in backend's allowed origins (`server/src/main.ts`) +- Check nginx proxy configuration + +### API Not Proxying +- Verify nginx.conf has correct backend URL +- Rebuild and redeploy frontend after changing `nginx.conf` + +### Image Generation Fails +- Verify GEMINI_API_KEY has Imagen API access (requires billing) +- Check quota limits in Google Cloud Console + +### Cold Start Times +- Frontend: ~2-3 seconds on first request (due to scale-to-zero) +- Backend: ~3-5 seconds on first request (due to scale-to-zero) +- Subsequent requests: <1 second diff --git a/DEPLOYMENT_LOCAL.md b/DEPLOYMENT_LOCAL.md new file mode 100644 index 0000000..af372d3 --- /dev/null +++ b/DEPLOYMENT_LOCAL.md @@ -0,0 +1,71 @@ +# Local Deployment Setup + +## System Requirements +- **Node.js** v18+ +- **npm** v8+ +- Modern browser (Chrome, Firefox, Safari) + +## Installation & Setup + +1. **Clone the Project** + ```bash + git clone https://github.com/gracetyy/CraftscapeHK + cd CraftscapeHK + ``` + +2. **Install Dependencies** + ```bash + npm install + ``` + > The root `postinstall` hook installs the NestJS backend in `server/` so you only run this once. + +3. **Set Environment Variables** + Create a `.env` file in the repository root with the following content: + ```bash + GEMINI_API_KEY="" + ``` + Both the Vite app and the NestJS API will consume it. + + You may also add this line to the `.env` file to override the model used in image generation (default: `gemini-2.5-flash-image`). + ```bash + GOOGLE_AI_IMAGE_MODEL="" + ``` + +4. **Seed Database** + ```bash + npm run server:seed + ``` + +5. **Run the Full Stack with Auto-Restarting env files** + ```bash + npm run dev:stack:watch + ``` + +6. **Access the Application** + - 🌐 **Frontend**: http://localhost:3000 + - 🚀 **Backend API**: http://localhost:3001/api + +## Helpful npm Scripts +- `npm run dev:stack` – run frontend (`vite`) and backend (`nest start:dev`) together without env watching. +- `npm run dev:stack:watch` – same as above but restarts both processes whenever `.env` files change. +- `npm run build:stack` – produce production builds for the Vite app and the NestJS server in one step. +- `npm run server:start` – boot the compiled NestJS server (`npm run server:build` first if needed). + +## AI Setup + +### Environment variables +- `GEMINI_API_KEY` — Google AI Studio API key +- `GOOGLE_AI_IMAGE_MODEL` — optional override for Imagen model id + +Place the variable in your shell or a .env loaded by your process manager before starting the server. + +### Image model access +The endpoint `api/ai/generate-image` uses the Google GenAI SDK with model `gemini-2.5-flash-image`. +Google's Images API is not available on free keys. If you see: "Imagen API is only accessible to billed users at this time." + +Do this: +1. Go to Google AI Studio > API Keys > Create or select your key +2. Upgrade to a paid/billing-enabled key and ensure Images API access is enabled for that key +3. Copy the key and export it as `GEMINI_API_KEY` on the server +4. Restart the NestJS server + diff --git a/README.md b/README.md index 4fd7ceb..89aa50b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,24 @@ # Craftscape HK (藝守) +![Thumbnail](assets/readme/Thumbnail.png) + ## Live Demo -**Try our app on [https://craftscape-hk.vercel.app/](https://craftscape-hk.vercel.app/)!** +> [!IMPORTANT] +> **Try our app [here](https://craftscape-frontend-998275462099.us-central1.run.app/)!** + +## Table of Contents +- [Inspiration](#inspiration) +- [Introduction](#introduction) +- [Architecture Overview](#architecture-overview) + - [Frontend](#frontend) + - [Backend](#backend) + - [Cloud Run Deployment Architecture](#cloud-run-deployment-architecture) + - [Data Flow](#data-flow) +- [Challenges & Accomplishments](#challenges-we-ran-into) +- [What We Learned](#what-we-learned) +- [What's Next](#whats-next-for-craftscape-hk) +- [Quick Start (Local Development)](#quick-start-local-development) +- [Deployment to Google Cloud Run](#deployment-to-google-cloud-run) ## Inspiration Hong Kong’s traditional crafts—such as **hand-carved mahjong tiles, painted porcelain, cheongsam, and neon sign**—are fading due to shrinking markets and an aging artisan community. @@ -10,7 +27,7 @@ Hong Kong’s traditional crafts—such as **hand-carved mahjong tiles, painted We wanted to build a bridge between the past and the future, enabling young people and global visitors to not only appreciate but also interact with these disappearing arts, and translating the appreciation into tangible support. **Because the most meaningful way to preserve a craft is to create a thriving economy around it.** -## What it does +## Introduction Craftscape HK is an **AI + AR e-commerce platform** where users can: - Explore craft stories through a swipe-card interface (like Tinder). - Use the **AI Creation Studio** to design their own craft pieces and directly commission artisans to bring them to life. @@ -18,22 +35,95 @@ Craftscape HK is an **AI + AR e-commerce platform** where users can: - Access a **city-wide cultural events calendar** for exhibitions, workshops, and community activities. - Support artisans by purchasing products, attending workshops, and visiting virtual/AR exhibitions. -## How we built it -- **Frontend**: A React 19 + TypeScript interface bundled with Vite, styled through Tailwind CSS (via CDN) and Framer Motion for micro-interactions to deliver the swipeable, mobile-first experience. -- **Backend - Platform APIs & data layer**: Modular NestJS endpoints for crafts, products, events, orders, and messaging run on TypeORM with a SQLite store, exposing REST routes that the frontend consumes via a typed API client with authenticated fetch helpers and offline fallbacks. -- **Function - AI Creation Studio**: A NestJS AI microservice wraps Google’s Imagen 4.0 (exposed through the @google/genai SDK) and returns base64 renders that the AiStudio view consumes and stores in the shared context, so artisans receive customizable design briefs. -- **Function - AR & experiential layer**: The Play screen ships downloadable USDZ assets (scanned by Reality Composer with iPhone)so visitors can launch Quick Look/WebAR sessions from their phones, complementing the narrative exhibition content in-app. +## Architecture Overview +Craftscape HK follows a cloud-native microservices architecture deployed on Google Cloud Platform, via Cloud Run for serverless container orchestration. + +### Architecture Diagram +![Architecture Diagram](assets/readme/CraftscapeHK_ArchDiagram.png) + +### Frontend +- **Technology Stack**: React 19, TypeScript, Vite, Tailwind CSS, Framer Motion +- **Container**: Multi-stage Docker build using Node.js (builder) + Nginx (production) +- **Web Server**: Nginx on port 8080 with: + - Client-side routing support (React Router) + - API proxy to backend service (`/api/*` routes) + - Static asset caching and gzip compression + - Security headers (X-Frame-Options, CSP, etc.) +- **Resource Allocation**: 256Mi RAM, 1 CPU, 60s timeout +- **Scaling**: Auto-scales 0-10 instances based on traffic + +### Backend +- **Technology Stack**: NestJS, TypeORM, SQLite, Google GenAI SDK +- **Container**: Single-stage Docker build with Node.js 20 (includes build dependencies for canvas) +- **API Modules**: + - **Crafts**: Traditional craft catalog and stories + - **Products**: E-commerce product listings + - **Events**: Cultural events and workshops calendar + - **Orders**: Purchase and commission management + - **Messages**: Artisan-customer communication + - **AI**: Multimodal prompt to **image** generation via Nano Banana (`gemini-2.5-flash-image`) + **Text** generation via `gemini-2.0-flash-exp` + - **Admin**: Database seeding and management endpoints for demo +- **Database**: SQLite bundled within container (seeded on deployment) +- **Resource Allocation**: 512Mi RAM, 1 CPU, 300s timeout +- **Scaling**: Auto-scales 0-10 instances based on traffic +- **CORS**: Configured for frontend, localhost, and development origins + +### Cloud Run Deployment Architecture + +#### **Container Registry (GCR)** +- Images stored in Google Container Registry at `gcr.io/$PROJECT_ID/` +- Each build tagged with both commit SHA and `latest` +- Separated images for frontend and backend + +#### Deployment Pipeline (Cloud Build) +The `cloudbuild.yaml` defines an automated CI/CD pipeline: + +1. **Build Phase** + - Builds frontend Docker image (`Dockerfile` at root) + - Builds backend Docker image (`server/Dockerfile`) + - Images are multi-stage optimized for production + +2. **Push Phase** + - Pushes both images to GCR with commit SHA tags + - Updates `latest` tags for easy rollback + +3. **Deploy Phase** + - Deploys backend to Cloud Run (`craftscape-backend`) + - Deploys frontend to Cloud Run (`craftscape-frontend`) + - Injects environment variables (`GEMINI_API_KEY`) + - Configures resource limits and auto-scaling + +4. **Initialization Phase** + - Waits for backend service to be healthy + - Seeds SQLite database via `/admin/seed` endpoint + - Validates deployment success + +The build pipeline is triggered automatically by the **Cloud Run Trigger** on each push to the main branch of the GitHub repository. + + + +### Data Flow + +1. **User Request** → Frontend Cloud Run service (Nginx) +2. **Static Content** → Served directly from Nginx +3. **API Request** (`/api/*`) → Proxied to Backend Cloud Run service +4. **Backend Processing** → NestJS handles request, queries SQLite +5. **AI Generation** → Backend calls Gemini API +6. **Response** → Data flows back through proxy to frontend +7. **AR Content** → USDZ files served as static assets +8. **Scheduled Maintenance** → Cloud Scheduler triggers monthly cleanup job to only keep the last 5 versions of each container image in GCR + ## Challenges we ran into -- Limited digital archives for crafts like hand-carved mahjong required manual data collection. -- Many artisans are elderly and unfamiliar with digital tools, so onboarding needed special care and training. -- Balancing **AI-generated creativity** with respect for authentic craft aesthetics was challenging. -- Ensuring sustainability: making the platform engaging for users while providing artisans with fair income. +- Limited digital archives for crafts like hand-carved mahjong required **manual data collection**. +- Many artisans are **elderly** and unfamiliar with digital tools, so onboarding needed special care and training. +- Balancing **AI-generated creativity** with respect for **authentic craft aesthetics** was challenging. +- Ensuring **sustainability** by making the platform engaging for users while providing artisans with fair income. ## Accomplishments that we're proud of -- Built an early prototype of the **AI Creation Studio** that generates personalized craft designs. +- Built a comprehensive prototype of the **AI Creation Studio** that generates **unique personalized craft designs** for different kinds of crafts. - Successfully piloted a working **AR exhibition demo** with 360° artifact viewing and real-world photo integration. -- Engaged real artisans in co-design workshops to validate cultural and practical feasibility. +- Engaged real artisans in **co-design workshops** to validate cultural and practical feasibility. - Developed a model for integrating cultural heritage into everyday digital life. ## What we learned @@ -43,131 +133,23 @@ Craftscape HK is an **AI + AR e-commerce platform** where users can: - Community collaboration is as important as technical innovation. ## What's next for Craftscape HK -- Expand our dataset of traditional crafts by partnering with museums, NGOs, and cultural heritage groups. +- Expand our dataset of traditional crafts by **partnering** with museums, NGOs, and cultural heritage groups. - Refine the **AI Creation Studio** to support more customization and multi-modal input (sketch + text). - Launch **pilot AR exhibitions** in collaboration with local cultural centers and schools. -- Explore monetization pathways to ensure artisans benefit directly from sales and commissions. +- Explore **monetization** pathways to ensure artisans benefit directly from sales and commissions. - Scale Craftscape HK into a **global platform for cultural heritage preservation**, starting with Hong Kong but extending to other endangered crafts worldwide. -## Quick Start - -### System Requirements -- **Node.js** v18+ -- **npm** v8+ -- Modern browser (Chrome, Firefox, Safari) - -### Installation & Setup - -1. **Clone the Project** - ```bash - git clone https://github.com/gracetyy/CraftscapeHK - cd CraftscapeHK - ``` - -2. **Install Dependencies** - ```bash - npm install - ``` - > The root `postinstall` hook installs the NestJS backend in `server/` so you only run this once. - -3. **Set Environment Variables** - Create a `.env` file in the repository root with the following content: - ```bash - GEMINI_API_KEY="" - ``` - Both the Vite app and the NestJS API will consume it. - - You may also put add this line to the `.env` file to override the model used in image generation (default: `gemini-2.5-flash-latest`). - ```bash - GOOGLE_AI_IMAGE_MODEL="" - ``` - -4. **Seed Database** - ```bash - npm run server:seed - ``` - -5. **Run the Full Stack with Auto-Restarting env files** - ```bash - npm run dev:stack:watch - ``` - -6. **Access the Application** - - 🌐 **Frontend**: http://localhost:3000 - - 🚀 **Backend API**: http://localhost:3001/api - -### Helpful npm Scripts -- `npm run dev:stack` – run frontend (`vite`) and backend (`nest start:dev`) together without env watching. -- `npm run dev:stack:watch` – same as above but restarts both processes whenever `.env` files change. -- `npm run build:stack` – produce production builds for the Vite app and the NestJS server in one step. -- `npm run server:start` – boot the compiled NestJS server (`npm run server:build` first if needed). - -### AI Setup - -#### Environment variables -- GEMINI_API_KEY — Google AI Studio API key -- GOOGLE_AI_IMAGE_MODEL — optional override for Imagen model id - -Place the variable in your shell or a .env loaded by your process manager before starting the server. - -#### Images API (Imagen 4) access -The endpoint api/ai/generate-image uses the Google GenAI SDK with model gemini-2.5-flash-latest. -Google’s Images API is not available on free keys. If you see: "Imagen API is only accessible to billed users at this time." - -Do this: -1. Go to Google AI Studio > API Keys > Create or select your key -2. Upgrade to a paid/billing-enabled key and ensure Images API access is enabled for that key -3. Copy the key and export it as GEMINI_API_KEY on the server -4. Restart the NestJS server - -#### Troubleshooting -- 400 INVALID_ARGUMENT with billed-users message: your key isn’t Images-enabled. Use a paid AI Studio key. -- 401 or 403: wrong key, missing export, or project/org restrictions. -- Region errors: Images is only in certain regions; AI Studio routes automatically. Vertex requires explicit regions. - -## Deployment - -### Production Deployment -Both frontend and backend are containerized and deployed to Google Cloud Run: +## Quick Start (Local Development) -**Quick Deploy (Both Services)** -```bash -npm run deploy:all -``` +For comprehensive cloud deployment instructions, see **[DEPLOYMENT_LOCAL.md](DEPLOYMENT_LOCAL.md)**. -**Deploy Individual Services** -```bash -# Deploy frontend only -npm run deploy:frontend +## Deployment to Google Cloud Run -# Deploy backend only -npm run deploy:backend -``` +For comprehensive cloud deployment instructions, see **[DEPLOYMENT_CLOUD.md](DEPLOYMENT_CLOUD.md)**. -**Architecture** -- Frontend: React + Nginx on Cloud Run (Port 8080) -- Backend: NestJS API on Cloud Run (Port 8080) -- Database: SQLite (bundled with backend) -- Container Registry: Google Container Registry (GCR) - - -#### Local Docker Testing -```bash -# Test frontend container -npm run docker:test:frontend - -# Test backend container -npm run docker:test - -# Or manually -docker build -t craftscape-frontend . -docker run -p 8080:8080 craftscape-frontend -``` - -## License -Released under the MIT License. +---

🎨 Made with ❤️ for Hong Kong Traditional Crafts 🇭🇰

由 Winter Club 團隊開發

-
\ No newline at end of file + diff --git a/assets/readme/CraftscapeHK_ArchDiagram.png b/assets/readme/CraftscapeHK_ArchDiagram.png new file mode 100644 index 0000000..de7b481 Binary files /dev/null and b/assets/readme/CraftscapeHK_ArchDiagram.png differ diff --git a/assets/readme/Thumbnail.png b/assets/readme/Thumbnail.png new file mode 100644 index 0000000..41e236b Binary files /dev/null and b/assets/readme/Thumbnail.png differ diff --git a/cloudbuild-cleanup.yaml b/cloudbuild-cleanup.yaml new file mode 100644 index 0000000..51ed786 --- /dev/null +++ b/cloudbuild-cleanup.yaml @@ -0,0 +1,56 @@ +steps: + # Clean up old backend images + - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + id: 'cleanup-backend' + entrypoint: 'bash' + args: + - '-c' + - | + echo "Cleaning up craftscape-backend images..." + + # Get all digests (skip first 5 newest) + DIGESTS=$$(gcloud container images list-tags gcr.io/craftscapehk/craftscape-backend \ + --format="get(digest)" \ + --sort-by="~timestamp" \ + --limit=100 | tail -n +6) + + if [ -z "$$DIGESTS" ]; then + echo "No old images to delete for backend" + else + echo "$$DIGESTS" | while read DIGEST; do + echo "Deleting backend: $$DIGEST" + gcloud container images delete "gcr.io/craftscapehk/craftscape-backend@$$DIGEST" \ + --quiet --force-delete-tags || echo "Failed to delete $$DIGEST" + done + fi + + # Clean up old frontend images + - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + id: 'cleanup-frontend' + entrypoint: 'bash' + args: + - '-c' + - | + echo "Cleaning up craftscape-frontend images..." + + # Get all digests (skip first 5 newest) + DIGESTS=$$(gcloud container images list-tags gcr.io/craftscapehk/craftscape-frontend \ + --format="get(digest)" \ + --sort-by="~timestamp" \ + --limit=100 | tail -n +6) + + if [ -z "$$DIGESTS" ]; then + echo "No old images to delete for frontend" + else + echo "$$DIGESTS" | while read DIGEST; do + echo "Deleting frontend: $$DIGEST" + gcloud container images delete "gcr.io/craftscapehk/craftscape-frontend@$$DIGEST" \ + --quiet --force-delete-tags || echo "Failed to delete $$DIGEST" + done + fi + +options: + logging: CLOUD_LOGGING_ONLY + machineType: 'E2_HIGHCPU_8' + +timeout: '600s' diff --git a/components/ArtisanBottomNav.tsx b/components/ArtisanBottomNav.tsx index 7b15c66..efcc4b9 100644 --- a/components/ArtisanBottomNav.tsx +++ b/components/ArtisanBottomNav.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { motion } from "framer-motion"; import { ArtisanTab } from "../enums"; import { useLanguage } from "../contexts/LanguageContext"; @@ -11,10 +12,11 @@ const NavItem: React.FC<{ }> = ({ label, tab, activeTab, setActiveTab, children }) => { const isActive = activeTab === tab; return ( - + ); }; @@ -44,138 +46,135 @@ const ArtisanBottomNav: React.FC<{ }> = ({ activeTab, setActiveTab }) => { const { t } = useLanguage(); return ( - + ); }; diff --git a/components/AuthModal.tsx b/components/AuthModal.tsx new file mode 100644 index 0000000..01da2d5 --- /dev/null +++ b/components/AuthModal.tsx @@ -0,0 +1,304 @@ +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useAuth } from '../contexts/AuthContext'; +import { useLanguage } from '../contexts/LanguageContext'; + +interface AuthModalProps { + isOpen: boolean; + onClose: () => void; + initialMode?: 'login' | 'register'; +} + +const AuthModal: React.FC = ({ isOpen, onClose, initialMode = 'login' }) => { + const [mode, setMode] = useState<'login' | 'register'>(initialMode); + const [email, setEmail] = useState(''); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [role, setRole] = useState<'user' | 'artisan'>('user'); + const [error, setError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const { login, register } = useAuth(); + const { language } = useLanguage(); + + const translations = { + zh: { + login: '登入', + register: '註冊', + email: '電郵地址', + username: '用戶名稱', + password: '密碼', + confirmPassword: '確認密碼', + userRole: '用戶', + artisanRole: '工匠', + selectRole: '選擇身份', + noAccount: '還沒有帳戶?', + hasAccount: '已有帳戶?', + createAccount: '建立帳戶', + loginNow: '立即登入', + forgotPassword: '忘記密碼?', + passwordMismatch: '密碼不匹配', + loginSuccess: '登入成功!', + registerSuccess: '註冊成功!', + welcomeBack: '歡迎回來', + createYourAccount: '建立您的帳戶', + continueAs: '以以下身份繼續', + }, + en: { + login: 'Log In', + register: 'Sign Up', + email: 'Email Address', + username: 'Username', + password: 'Password', + confirmPassword: 'Confirm Password', + userRole: 'Explorer', + artisanRole: 'Artisan', + selectRole: 'I am a...', + noAccount: "Don't have an account?", + hasAccount: 'Already have an account?', + createAccount: 'Create Account', + loginNow: 'Log In', + forgotPassword: 'Forgot Password?', + passwordMismatch: 'Passwords do not match', + loginSuccess: 'Logged in successfully!', + registerSuccess: 'Account created successfully!', + welcomeBack: 'Welcome Back', + createYourAccount: 'Create Your Account', + continueAs: 'Continue as', + }, + }; + + const t = translations[language]; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setIsSubmitting(true); + + try { + if (mode === 'register') { + if (password !== confirmPassword) { + setError(t.passwordMismatch); + return; + } + await register(email, username, password, role); + } else { + await login(email, password); + } + onClose(); + } catch (err: any) { + setError(err.message || 'An error occurred'); + } finally { + setIsSubmitting(false); + } + }; + + const resetForm = () => { + setEmail(''); + setUsername(''); + setPassword(''); + setConfirmPassword(''); + setError(null); + }; + + const switchMode = () => { + resetForm(); + setMode(mode === 'login' ? 'register' : 'login'); + }; + + if (!isOpen) return null; + + return ( + + + e.stopPropagation()} + > + {/* Handle bar for mobile */} +
+ + {/* Header */} +
+

+ {mode === 'login' ? t.welcomeBack : t.createYourAccount} +

+
+ + {/* Form */} +
+ {/* Error message */} + + {error && ( + +

{error}

+
+ )} +
+ + {/* Email */} +
+ + setEmail(e.target.value)} + required + className="w-full h-14 px-5 bg-[var(--color-surface)]/60 backdrop-blur-sm border border-white/10 rounded-2xl text-[var(--color-text-primary)] font-medium focus:ring-2 focus:ring-[var(--color-primary-accent)]/30 focus:border-transparent transition-all ios-shadow" + placeholder="hello@example.com" + /> +
+ + {/* Username (register only) */} + {mode === 'register' && ( +
+ + setUsername(e.target.value)} + required + minLength={3} + className="w-full h-14 px-5 bg-[var(--color-surface)]/60 backdrop-blur-sm border border-white/10 rounded-2xl text-[var(--color-text-primary)] font-medium focus:ring-2 focus:ring-[var(--color-primary-accent)]/30 focus:border-transparent transition-all ios-shadow" + placeholder="craftlover" + /> +
+ )} + + {/* Password */} +
+ + setPassword(e.target.value)} + required + minLength={8} + className="w-full h-14 px-5 bg-[var(--color-surface)]/60 backdrop-blur-sm border border-white/10 rounded-2xl text-[var(--color-text-primary)] font-medium focus:ring-2 focus:ring-[var(--color-primary-accent)]/30 focus:border-transparent transition-all ios-shadow" + placeholder="••••••••" + /> +
+ + {/* Confirm Password (register only) */} + {mode === 'register' && ( +
+ + setConfirmPassword(e.target.value)} + required + minLength={8} + className="w-full h-14 px-5 bg-[var(--color-surface)]/60 backdrop-blur-sm border border-white/10 rounded-2xl text-[var(--color-text-primary)] font-medium focus:ring-2 focus:ring-[var(--color-primary-accent)]/30 focus:border-transparent transition-all ios-shadow" + placeholder="••••••••" + /> +
+ )} + + {/* Role selection (register only) */} + {mode === 'register' && ( +
+ +
+ + +
+
+ )} + + {/* Submit button */} + + {isSubmitting ? ( +
+ ) : mode === 'login' ? ( + t.login + ) : ( + t.register + )} + + + {/* Switch mode */} +
+

+ {mode === 'login' ? t.noAccount : t.hasAccount}{' '} + +

+
+ + {/* Forgot password (login only) */} + {mode === 'login' && ( +
+ +
+ )} + + + + + ); +}; + +export default AuthModal; + diff --git a/components/BottomNav.tsx b/components/BottomNav.tsx index 1605a09..eda461a 100644 --- a/components/BottomNav.tsx +++ b/components/BottomNav.tsx @@ -32,14 +32,12 @@ const NavItem: React.FC<{ const BottomNav: React.FC<{ activeTab: Tab; setActiveTab: (tab: Tab) => void; }> = ({ activeTab, setActiveTab }) => { const { t } = useLanguage(); return ( -