From 9dedad5e9ad0196ecd677958df8a07eaad6fb554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:04:42 +0000 Subject: [PATCH 1/2] Initial plan From 1d8163f81016d0197c91c649c952fc9eb4c66e49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:15:52 +0000 Subject: [PATCH 2/2] Add analytics route, web/main.js API module, and project README Co-authored-by: Vishnumgit <261896239+Vishnumgit@users.noreply.github.com> --- README.md | 83 +++++++++++++ backend/src/routes/analytics.js | 69 +++++++++++ backend/src/server.js | 6 +- database/001_init_schema.sql | 1 + docs/API.md | 42 +++++++ web/index.html | 165 +------------------------- web/main.js | 204 ++++++++++++++++++++++++++++++++ 7 files changed, 404 insertions(+), 166 deletions(-) create mode 100644 backend/src/routes/analytics.js create mode 100644 web/main.js diff --git a/README.md b/README.md index 8b13789..6c79a71 100644 --- a/README.md +++ b/README.md @@ -1 +1,84 @@ +# QR to 3D AR Hybrid Visualization System +Scan a QR code → view the linked product as an interactive 3D model in your browser (WebAR). + +## Architecture + +``` +Browser (WebAR) → Express API (Render) → PostgreSQL (Supabase) + ↑ + QR code scan +``` + +## Quick Start (Docker) + +```bash +git clone https://github.com/Vishnumgit/analysis-feedback-repo.git +cd analysis-feedback-repo +cp backend/.env.example backend/.env # fill in values +docker compose up --build +``` + +| Service | URL | +|----------|-----------------------------------| +| API | http://localhost:3000/api/health | +| Frontend | http://localhost:5173 | +| Database | localhost:5432 | + +## Deployment + +See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for step-by-step instructions to deploy on: + +- **Database** – Supabase (free tier) +- **Backend API** – Render (free tier) +- **Frontend** – Vercel (free tier) + +## API Reference + +See [docs/API.md](docs/API.md) for all endpoints. + +Key endpoints: + +| Method | Path | Description | +|--------|--------------------------------|-------------------------| +| GET | /api/health | Server health check | +| GET | /api/products | List products | +| GET | /api/products/:id | Get product by ID | +| GET | /api/qr/:qrCode | Look up QR code | +| POST | /api/qr/:qrCode/scan | Record a scan event | +| POST | /api/analytics/session | Log an AR session | + +## Project Structure + +``` +├── backend/ Express.js API +│ ├── src/ +│ │ ├── server.js Entry point +│ │ ├── config/database.js +│ │ └── routes/ +│ │ ├── qr.js +│ │ ├── products.js +│ │ └── analytics.js +│ ├── Dockerfile +│ └── package.json +├── web/ WebAR frontend +│ ├── index.html +│ ├── main.js Three.js viewer + API helpers +│ └── package.json +├── database/ +│ ├── 001_init_schema.sql Table definitions +│ └── seed_data.sql Sample products & QR codes +├── docs/ +│ ├── API.md +│ ├── DEPLOYMENT.md +│ └── SETUP.md +└── docker-compose.yml +``` + +## Local Development + +See [docs/SETUP.md](docs/SETUP.md) for detailed instructions. + +## License + +MIT diff --git a/backend/src/routes/analytics.js b/backend/src/routes/analytics.js new file mode 100644 index 0000000..f3e4070 --- /dev/null +++ b/backend/src/routes/analytics.js @@ -0,0 +1,69 @@ +'use strict'; + +const express = require('express'); +const { body, validationResult } = require('express-validator'); +const pool = require('../config/database'); + +const router = express.Router(); + +const validate = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(422).json({ errors: errors.array() }); + } + next(); +}; + +/** + * POST /api/analytics/session + * Log an AR session event. + * + * Body: + * product_id {number} required + * platform {string} optional e.g. "web" | "mobile" + * duration {number} optional seconds spent in AR + * user_agent {string} optional + * latitude {number} optional + * longitude {number} optional + */ +router.post( + '/session', + [ + body('product_id').isInt({ min: 1 }).toInt(), + body('platform').optional().isString().trim().isLength({ max: 50 }), + body('duration').optional().isFloat({ min: 0 }).toFloat(), + body('user_agent').optional().isString().trim().isLength({ max: 500 }), + body('latitude').optional().isFloat({ min: -90, max: 90 }).toFloat(), + body('longitude').optional().isFloat({ min: -180, max: 180 }).toFloat(), + ], + validate, + async (req, res) => { + try { + const { product_id, platform, duration, user_agent, latitude, longitude } = req.body; + + const result = await pool.query( + `INSERT INTO ar_sessions (product_id, user_agent, latitude, longitude, duration_sec, platform) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING session_id, product_id, platform, created_at`, + [ + product_id, + user_agent || null, + latitude || null, + longitude || null, + duration != null ? Math.round(duration) : null, + platform || null, + ] + ); + + res.status(201).json({ + success: true, + data: result.rows[0], + }); + } catch (err) { + console.error('POST /api/analytics/session error:', err.message); + res.status(500).json({ error: 'Internal server error' }); + } + } +); + +module.exports = router; diff --git a/backend/src/server.js b/backend/src/server.js index ce4ddd6..1ef82a5 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -8,8 +8,9 @@ const helmet = require('helmet'); const morgan = require('morgan'); const rateLimit = require('express-rate-limit'); -const qrRoutes = require('./routes/qr'); -const productRoutes = require('./routes/products'); +const qrRoutes = require('./routes/qr'); +const productRoutes = require('./routes/products'); +const analyticsRoutes = require('./routes/analytics'); const app = express(); @@ -71,6 +72,7 @@ app.get('/api/health', (_req, res) => { app.use('/api/qr', qrRoutes); app.use('/api/products', productRoutes); +app.use('/api/analytics', analyticsRoutes); // ── 404 handler ─────────────────────────────────────────────── app.use((_req, res) => { diff --git a/database/001_init_schema.sql b/database/001_init_schema.sql index 8b11941..baa00d3 100644 --- a/database/001_init_schema.sql +++ b/database/001_init_schema.sql @@ -56,6 +56,7 @@ CREATE TABLE IF NOT EXISTS ar_sessions ( latitude FLOAT, longitude FLOAT, duration_sec INT, -- seconds spent in AR + platform VARCHAR(50), -- e.g. 'web' | 'mobile' created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); diff --git a/docs/API.md b/docs/API.md index b747131..4e8a7fc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -170,6 +170,48 @@ Create a new product entry (admin use). --- +## Analytics + +### `POST /api/analytics/session` + +Log an AR session event (called automatically by the WebAR viewer). + +**Request body** +```json +{ + "product_id": 1, + "platform": "web", + "duration": 45, + "user_agent": "Mozilla/5.0…", + "latitude": 37.7749, + "longitude": -122.4194 +} +``` + +| Field | Type | Required | Description | +|--------------|---------|----------|------------------------------------| +| `product_id` | integer | ✅ | ID of the viewed product | +| `platform` | string | – | `web` or `mobile` | +| `duration` | number | – | Seconds spent in AR | +| `user_agent` | string | – | Browser user-agent string | +| `latitude` | number | – | GPS latitude (–90 to 90) | +| `longitude` | number | – | GPS longitude (–180 to 180) | + +**Response 201** +```json +{ + "success": true, + "data": { + "session_id": 1, + "product_id": 1, + "platform": "web", + "created_at": "2026-03-17T12:00:00.000Z" + } +} +``` + +--- + ## Error Codes | Code | Meaning | diff --git a/web/index.html b/web/index.html index 4f091b0..17c96f8 100644 --- a/web/index.html +++ b/web/index.html @@ -128,169 +128,6 @@