Computer quantistici: siamo davvero alla vigilia del grande salto?

Immagine
  COMPUTER QUANTISTICI: SIAMO DAVVERO ALLA VIGILIA DEL GRANDE SALTO? Dopo anni di promesse, il quantum computing entra nella fase in cui deve dimostrare di saper fare qualcosa di realmente utile Per molti anni il computer quantistico è stato contemporaneamente una macchina reale e una promessa. Reale, perché nei laboratori funzionavano già processori capaci di controllare decine e poi centinaia di qubit. Promessa, perché fra l'esecuzione di esperimenti spettacolari e la disponibilità di una macchina capace di risolvere problemi industrialmente importanti meglio dei migliori sistemi classici rimaneva una distanza enorme. Nel 2026 quella distanza non è scomparsa. Ma qualcosa è cambiato. Non stiamo ancora entrando nell'epoca nella quale i computer quantistici sostituiranno quelli tradizionali — e probabilmente non entreremo mai in un'epoca formulata in questi termini. Stiamo invece arrivando al punto nel quale diventa plausibile immaginare sistemi computazionali ibridi, costit...

Corso di JavaScript & Programmazione Web: 11 – Codice del sito di e-commerce

11 – Codice del sito di e-commerce

merchant-backend/
├─ package.json
├─ .env.example
└─ src/
   ├─ index.js
   ├─ config/
   │  └─ db.js
   ├─ models/
   │  ├─ User.js
   │  └─ Product.js
   ├─ controllers/
   │  ├─ authController.js
   │  └─ productController.js
   └─ routes/
      ├─ auth.js
      └─ products.js

{
  "name": "merchant-backend",
  "version": "1.0.0",
  "description": "Backend per sito commerciante con e-commerce minimale (Node/Express/MongoDB/JWT)",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "lint": "eslint . --ext .js"
  },
  "author": "Tuonome",
  "license": "MIT",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "dotenv": "^16.1.4",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "jsonwebtoken": "^9.0.0",
    "mongoose": "^7.5.0",
    "morgan": "^1.10.0",
    "multer": "^1.4.5-lts.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.22",
    "eslint": "^8.50.0"
  }
}

# Rename to .env and fill values
PORT=4000
MONGO_URI=mongodb://localhost:27017/merchantdb
JWT_SECRET=changeme_super_secret_jwt_key
JWT_EXPIRES_IN=7d
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=ChangeMe123!
UPLOADS_DIR=uploads


/**
 * Entry point for the backend server
 * - express app
 * - basic middlewares (cors, json, morgan)
 * - routes: /api/auth, /api/products
 * - global error handler
 */

require('dotenv').config();
require('express-async-errors'); // to allow throwing errors in async routes
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const path = require('path');

const connectDB = require('./config/db');
const authRoutes = require('./routes/auth');
const productRoutes = require('./routes/products');

const app = express();

// Connect DB
connectDB();

// Middlewares
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(morgan('dev'));

// Serve uploaded images (if using local storage)
const uploadsDir = process.env.UPLOADS_DIR || 'uploads';
app.use(`/static/${uploadsDir}`, express.static(path.join(__dirname, '..', uploadsDir)));

// API Routes
app.use('/api/auth', authRoutes);
app.use('/api/products', productRoutes);

// Health check
app.get('/api/health', (req, res) => res.json({ status: 'ok', time: new Date().toISOString() }));

// Global error handler
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
  console.error(err);
  const status = err.status || 500;
  const message = err.message || 'Internal Server Error';
  res.status(status).json({ error: message, stack: process.env.NODE_ENV === 'production' ? undefined : err.stack });
});

// Start server
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
  // create admin user on startup? optional, can be implemented
  console.log(`Server running on port ${PORT}`);
});


/**
 * MongoDB connection via mongoose
 */

const mongoose = require('mongoose');

const connectDB = async () => {
  const uri = process.env.MONGO_URI;
  if (!uri) {
    console.error('MONGO_URI not set in environment');
    process.exit(1);
  }
  try {
    await mongoose.connect(uri, {
      // options (mongoose 7+ has sensible defaults)
    });
    console.log('MongoDB connected');
  } catch (err) {
    console.error('MongoDB connection error:', err);
    process.exit(1);
  }
};

module.exports = connectDB;

/**
 * User model (basic for authentication)
 * Roles: user, admin
 */

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true, maxlength: 100 },
  email: { type: String, required: true, unique: true, lowercase: true, trim: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' },
  createdAt: { type: Date, default: Date.now }
});

// Hash password before save
UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// Instance method to compare password
UserSchema.methods.comparePassword = async function (candidate) {
  return bcrypt.compare(candidate, this.password);
};

module.exports = mongoose.model('User', UserSchema);



/**
 * Product model for e-commerce items
 * Images: store relative path or URL
 */

const mongoose = require('mongoose');

const ProductSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true, maxlength: 200 },
  slug: { type: String, required: true, unique: true }, // url-friendly
  description: { type: String, default: '' },
  price: { type: Number, required: true, min: 0 },
  currency: { type: String, default: 'EUR' },
  stock: { type: Number, default: 0 },
  category: { type: String, default: 'general' },
  images: [{ type: String }], // store URLs or local paths (/static/uploads/...)
  featured: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

ProductSchema.pre('save', function (next) {
  this.updatedAt = Date.now();
  next();
});

module.exports = mongoose.model('Product', ProductSchema);


/**
 * Auth controller: register, login, profile
 */

const jwt = require('jsonwebtoken');
const User = require('../models/User');

const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';

const signToken = (user) => {
  return jwt.sign({ id: user._id, role: user.role, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
};

exports.register = async (req, res) => {
  const { name, email, password } = req.body;
  if (!name || !email || !password) return res.status(400).json({ error: 'name, email, password required' });

  const existing = await User.findOne({ email });
  if (existing) return res.status(409).json({ error: 'Email already registered' });

  const user = new User({ name, email, password });
  await user.save();

  const token = signToken(user);
  res.status(201).json({ token, user: { id: user._id, name: user.name, email: user.email, role: user.role } });
};

exports.login = async (req, res) => {
  const { email, password } = req.body;
  if (!email || !password) return res.status(400).json({ error: 'email and password required' });

  const user = await User.findOne({ email });
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const match = await user.comparePassword(password);
  if (!match) return res.status(401).json({ error: 'Invalid credentials' });

  const token = signToken(user);
  res.json({ token, user: { id: user._id, name: user.name, email: user.email, role: user.role } });
};

exports.me = async (req, res) => {
  // expects req.user set by auth middleware
  const user = await User.findById(req.user.id).select('-password');
  res.json({ user });
};

/**
 * Product controller: CRUD operations
 * - list with pagination / filters
 * - get by slug / id
 * - create (admin)
 * - update (admin)
 * - delete (admin)
 *
 * Note: For image upload we will accept a multipart/form-data route in the route file using multer.
 */

const Product = require('../models/Product');
const slugify = (s) => s.toLowerCase().trim().replace(/\s+/g, '-').replace(/[^\w\-]+/g, '');

exports.list = async (req, res) => {
  const page = Math.max(1, parseInt(req.query.page) || 1);
  const limit = Math.min(100, parseInt(req.query.limit) || 12);
  const skip = (page - 1) * limit;

  const filter = {};
  if (req.query.category) filter.category = req.query.category;
  if (req.query.q) filter.$or = [
    { title: { $regex: req.query.q, $options: 'i' } },
    { description: { $regex: req.query.q, $options: 'i' } }
  ];

  const [items, total] = await Promise.all([
    Product.find(filter).sort({ featured: -1, createdAt: -1 }).skip(skip).limit(limit),
    Product.countDocuments(filter)
  ]);

  res.json({ items, meta: { page, limit, total, pages: Math.ceil(total / limit) } });
};

exports.getBySlug = async (req, res) => {
  const product = await Product.findOne({ slug: req.params.slug });
  if (!product) return res.status(404).json({ error: 'Product not found' });
  res.json({ product });
};

exports.create = async (req, res) => {
  const { title, description, price, currency = 'EUR', stock = 0, category = 'general', images = [] } = req.body;
  if (!title || price == null) return res.status(400).json({ error: 'title and price required' });

  const slug = slugify(title);
  const existing = await Product.findOne({ slug });
  let finalSlug = slug;
  if (existing) finalSlug = `${slug}-${Date.now().toString().slice(-4)}`;

  const p = new Product({
    title, slug: finalSlug, description, price: parseFloat(price), currency, stock: parseInt(stock, 10), category, images
  });
  await p.save();
  res.status(201).json({ product: p });
};

exports.update = async (req, res) => {
  const updates = req.body;
  if (updates.title) updates.slug = slugify(updates.title);
  const product = await Product.findByIdAndUpdate(req.params.id, updates, { new: true });
  if (!product) return res.status(404).json({ error: 'Not found' });
  res.json({ product });
};

exports.remove = async (req, res) => {
  const product = await Product.findByIdAndDelete(req.params.id);
  if (!product) return res.status(404).json({ error: 'Not found' });
  res.json({ success: true });
};


/**
 * Auth routes
 */

const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { authMiddleware } = require('../utils/middleware'); // file middleware che aggiungeremo dopo

// Public
router.post('/register', authController.register);
router.post('/login', authController.login);

// Protected
router.get('/me', authMiddleware, authController.me);

module.exports = router;

/**
 * Product routes
 * - GET /      -> list, with query params
 * - GET /:slug -> get by slug
 * - POST /     -> create (admin)
 * - PUT /:id   -> update (admin)
 * - DELETE /:id -> delete (admin)
 *
 * Image uploads: optionally POST /:id/images (handled separately)
 */

const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
const { authMiddleware, adminOnly } = require('../utils/middleware');

// Public
router.get('/', productController.list);
router.get('/slug/:slug', productController.getBySlug);

// Protected admin
router.post('/', authMiddleware, adminOnly, productController.create);
router.put('/:id', authMiddleware, adminOnly, productController.update);
router.delete('/:id', authMiddleware, adminOnly, productController.remove);

module.exports = router;

/**
 * Utility middleware: authMiddleware (JWT), adminOnly
 */

const jwt = require('jsonwebtoken');
const User = require('../models/User');

const JWT_SECRET = process.env.JWT_SECRET || 'changeme';

exports.authMiddleware = async (req, res, next) => {
  const auth = req.headers.authorization;
  if (!auth || !auth.startsWith('Bearer ')) return res.status(401).json({ error: 'Missing token' });
  const token = auth.split(' ')[1];
  try {
    const payload = jwt.verify(token, JWT_SECRET);
    // attach user minimal info
    req.user = { id: payload.id, role: payload.role, email: payload.email };
    // optionally fetch full user if needed
    // req.userFull = await User.findById(payload.id).select('-password');
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

exports.adminOnly = (req, res, next) => {
  if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
  if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin only' });
  next();
};


"dependencies": {
  "...": "...",
  "stripe": "^11.0.0",
  "express-validator": "^7.0.1",
  "nodemailer": "^6.9.4"
}


(esegui npm install stripe express-validator nodemailer)

.env.example — aggiornamento

Aggiungi questi parametri:

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Email (opzionale)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your@email
SMTP_PASS=secret

# Admin seed
ADMIN_NAME=Admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=ChangeMe123!

1) Upload immagini — src/utils/upload.js

Crea src/utils/upload.js per gestire multer e storage locale (cartella uploads/).

const path = require('path');
const multer = require('multer');
const fs = require('fs');

const uploadsDir = process.env.UPLOADS_DIR || path.join(__dirname, '..', '..', 'uploads');

// assicurati che la cartella esista
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true });

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, uploadsDir);
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    const name = path.basename(file.originalname, ext).replace(/\s+/g, '-').toLowerCase();
    const unique = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(null, `${name}-${unique}${ext}`);
  }
});

const fileFilter = (req, file, cb) => {
  // accetta solo immagini
  if (/image\/(jpeg|png|gif|webp)/.test(file.mimetype)) cb(null, true);
  else cb(new Error('Only image files are allowed!'), false);
};

const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 } }); // 5MB

module.exports = { upload, uploadsDir };

2) Route per upload immagini — src/routes/uploads.js
const express = require('express');
const router = express.Router();
const { upload } = require('../utils/upload');
const { authMiddleware, adminOnly } = require('../utils/middleware');
const path = require('path');

router.post('/single', authMiddleware, adminOnly, upload.single('image'), (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
  // restituisci percorso pubblico relativo
  const publicPath = `/static/${process.env.UPLOADS_DIR || 'uploads'}/${req.file.filename}`;
  res.status(201).json({ url: publicPath, filename: req.file.filename });
});

router.post('/multiple', authMiddleware, adminOnly, upload.array('images', 8), (req, res) => {
  if (!req.files || req.files.length === 0) return res.status(400).json({ error: 'No files uploaded' });
  const urls = req.files.map(f => `/static/${process.env.UPLOADS_DIR || 'uploads'}/${f.filename}`);
  res.status(201).json({ urls });
});

module.exports = router;


Aggiungi la route in src/index.js:

const uploadRoutes = require('./routes/uploads');
app.use('/api/uploads', uploadRoutes);

3) Modello Cart e Order — src/models/Cart.js e src/models/Order.js
src/models/Cart.js

(Semplice rappresentazione lato backend — cart può essere memorizzata in DB oppure solo client-side; qui implementiamo una versione server-side associata a user)

const mongoose = require('mongoose');

const CartItemSchema = new mongoose.Schema({
  product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
  title: { type: String },
  price: { type: Number, required: true },
  qty: { type: Number, default: 1 },
  image: { type: String }
}, { _id: false });

const CartSchema = new mongoose.Schema({
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, unique: true },
  items: [CartItemSchema],
  updatedAt: { type: Date, default: Date.now }
});

CartSchema.pre('save', function (next) {
  this.updatedAt = Date.now();
  next();
});

module.exports = mongoose.model('Cart', CartSchema);

src/models/Order.js
const mongoose = require('mongoose');

const OrderItemSchema = new mongoose.Schema({
  product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
  title: String,
  price: Number,
  qty: Number,
  image: String
}, { _id: false });

const OrderSchema = new mongoose.Schema({
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  items: [OrderItemSchema],
  shipping: {
    name: String,
    address: String,
    city: String,
    postalCode: String,
    country: String
  },
  totalAmount: { type: Number, required: true },
  currency: { type: String, default: 'EUR' },
  payment: {
    provider: { type: String, default: 'stripe' },
    status: { type: String, enum: ['pending', 'paid', 'failed', 'refunded'], default: 'pending' },
    stripePaymentIntent: String
  },
  status: { type: String, enum: ['created','processing','shipped','completed','cancelled'], default: 'created' },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

OrderSchema.pre('save', function (next) {
  this.updatedAt = Date.now();
  next();
});

module.exports = mongoose.model('Order', OrderSchema);

4) Controller Carrello / Ordini — src/controllers/cartController.js e src/controllers/orderController.js
src/controllers/cartController.js
const Cart = require('../models/Cart');
const Product = require('../models/Product');

/**
 * API:
 * GET /api/cart        -> get cart for user
 * POST /api/cart/add   -> add product { productId, qty }
 * POST /api/cart/update -> update item qty { productId, qty }
 * POST /api/cart/remove -> remove item { productId }
 * POST /api/cart/clear -> clear cart
 */

exports.getCart = async (req, res) => {
  let cart = await Cart.findOne({ user: req.user.id }).populate('items.product');
  if (!cart) {
    cart = new Cart({ user: req.user.id, items: [] });
    await cart.save();
  }
  res.json({ cart });
};

exports.addToCart = async (req, res) => {
  const { productId, qty = 1 } = req.body;
  if (!productId) return res.status(400).json({ error: 'productId required' });
  const product = await Product.findById(productId);
  if (!product) return res.status(404).json({ error: 'Product not found' });

  let cart = await Cart.findOne({ user: req.user.id });
  if (!cart) cart = new Cart({ user: req.user.id, items: [] });

  const idx = cart.items.findIndex(i => i.product.toString() === productId);
  if (idx >= 0) {
    cart.items[idx].qty = Math.max(1, cart.items[idx].qty + Number(qty));
  } else {
    cart.items.push({
      product: product._id,
      title: product.title,
      price: product.price,
      qty: Number(qty),
      image: product.images && product.images[0] ? product.images[0] : ''
    });
  }
  await cart.save();
  res.json({ cart });
};

exports.updateItem = async (req, res) => {
  const { productId, qty } = req.body;
  if (!productId) return res.status(400).json({ error: 'productId required' });
  let cart = await Cart.findOne({ user: req.user.id });
  if (!cart) return res.status(404).json({ error: 'Cart not found' });

  const idx = cart.items.findIndex(i => i.product.toString() === productId);
  if (idx === -1) return res.status(404).json({ error: 'Item not in cart' });
  cart.items[idx].qty = Math.max(0, Number(qty));
  if (cart.items[idx].qty === 0) cart.items.splice(idx, 1);
  await cart.save();
  res.json({ cart });
};

exports.removeItem = async (req, res) => {
  const { productId } = req.body;
  if (!productId) return res.status(400).json({ error: 'productId required' });
  let cart = await Cart.findOne({ user: req.user.id });
  if (!cart) return res.status(404).json({ error: 'Cart not found' });

  cart.items = cart.items.filter(i => i.product.toString() !== productId);
  await cart.save();
  res.json({ cart });
};

exports.clearCart = async (req, res) => {
  await Cart.findOneAndUpdate({ user: req.user.id }, { $set: { items: [] } }, { upsert: true });
  res.json({ success: true });
};

src/controllers/orderController.js
const Order = require('../models/Order');
const Cart = require('../models/Cart');
const Product = require('../models/Product');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

/**
 * API:
 * POST /api/orders/create -> crea ordine a partire dal carrello (può anche avviare sessione Stripe)
 * GET  /api/orders/:id    -> dettagli ordine (user o admin)
 * GET  /api/orders        -> lista ordini (admin: tutti, user: propri)
 *
 * POST /api/orders/checkout-session -> crea sessione Stripe dalla cartella/ordine
 */

exports.createOrderFromCart = async (req, res) => {
  const cart = await Cart.findOne({ user: req.user.id }).populate('items.product');
  if (!cart || cart.items.length === 0) return res.status(400).json({ error: 'Cart empty' });

  // calcola totale
  const total = cart.items.reduce((s, it) => s + (it.price * it.qty), 0);

  const order = new Order({
    user: req.user.id,
    items: cart.items.map(i => ({
      product: i.product._id,
      title: i.title,
      price: i.price,
      qty: i.qty,
      image: i.image
    })),
    totalAmount: total,
    currency: 'EUR',
    payment: { provider: 'stripe', status: 'pending' },
    status: 'created'
  });

  await order.save();
  // opzionale: svuota carrello (o lascia al checkout)
  // await Cart.findOneAndUpdate({ user: req.user.id }, { items: [] });

  res.status(201).json({ order });
};

exports.getOrder = async (req, res) => {
  const order = await Order.findById(req.params.id).populate('user', 'name email');
  if (!order) return res.status(404).json({ error: 'Order not found' });
  // autorizzazione: user può vedere solo i suoi ordini
  if (req.user.role !== 'admin' && order.user._id.toString() !== req.user.id) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  res.json({ order });
};

exports.listOrders = async (req, res) => {
  const filter = {};
  if (req.user.role !== 'admin') filter.user = req.user.id;
  const items = await Order.find(filter).sort({ createdAt: -1 }).limit(200);
  res.json({ items });
};

/**
 * Crea sessione di checkout Stripe con items dal carrello o da ordine
 * POST /api/orders/checkout-session
 * body: { orderId? , successUrl, cancelUrl }
 */
exports.createCheckoutSession = async (req, res) => {
  const { orderId, successUrl, cancelUrl } = req.body;
  let order;
  if (orderId) {
    order = await Order.findById(orderId);
    if (!order) return res.status(404).json({ error: 'Order not found' });
  } else {
    // crea ordine provvisorio dal carrello
    const cart = await Cart.findOne({ user: req.user.id }).populate('items.product');
    if (!cart || cart.items.length === 0) return res.status(400).json({ error: 'Cart empty' });
    const total = cart.items.reduce((s, it) => s + (it.price * it.qty), 0);
    order = new Order({
      user: req.user.id,
      items: cart.items.map(i => ({
        product: i.product._id,
        title: i.title,
        price: i.price,
        qty: i.qty,
        image: i.image
      })),
      totalAmount: total,
      currency: 'EUR',
      payment: { provider: 'stripe', status: 'pending' },
      status: 'created'
    });
    await order.save();
  }

  // trasforma items in line_items per stripe
  const line_items = order.items.map(it => ({
    price_data: {
      currency: order.currency || 'EUR',
      product_data: { name: it.title, images: it.image ? [ `${req.protocol}://${req.get('host')}${it.image}` ] : [] },
      unit_amount: Math.round(it.price * 100)
    },
    quantity: it.qty
  }));

  // crea sessione
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    mode: 'payment',
    line_items,
    success_url: successUrl || `${req.protocol}://${req.get('host')}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: cancelUrl || `${req.protocol}://${req.get('host')}/checkout/cancel`,
    metadata: { orderId: order._id.toString(), userId: req.user.id }
  });

  // salva intent id / session id sull'ordine (opzionale)
  order.payment.stripePaymentIntent = session.payment_intent || session.id;
  await order.save();

  res.json({ sessionId: session.id, url: session.url });
};

5) Route ordini e carrello — src/routes/cart.js e src/routes/orders.js
src/routes/cart.js
const express = require('express');
const router = express.Router();
const cartController = require('../controllers/cartController');
const { authMiddleware } = require('../utils/middleware');

router.get('/', authMiddleware, cartController.getCart);
router.post('/add', authMiddleware, cartController.addToCart);
router.post('/update', authMiddleware, cartController.updateItem);
router.post('/remove', authMiddleware, cartController.removeItem);
router.post('/clear', authMiddleware, cartController.clearCart);

module.exports = router;


Aggiungi in src/index.js:

const cartRoutes = require('./routes/cart');
app.use('/api/cart', cartRoutes);

src/routes/orders.js
const express = require('express');
const router = express.Router();
const orderController = require('../controllers/orderController');
const { authMiddleware, adminOnly } = require('../utils/middleware');

router.post('/create', authMiddleware, orderController.createOrderFromCart);
router.post('/checkout-session', authMiddleware, orderController.createCheckoutSession);
router.get('/:id', authMiddleware, orderController.getOrder);
router.get('/', authMiddleware, orderController.listOrders);

module.exports = router;


Aggiungi in src/index.js:

const orderRoutes = require('./routes/orders');
app.use('/api/orders', orderRoutes);

6) Stripe Webhook — src/routes/webhooks.js e handler

Per ricevere eventi Stripe (pagamento riuscito ecc.) è meglio esporre endpoint separato e verificare firma.

src/routes/webhooks.js
const express = require('express');
const router = express.Router();
const Order = require('../models/Order');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

router.post('/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error('Webhook signature verification failed.', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // handle the event
  switch (event.type) {
    case 'checkout.session.completed':
      {
        const session = event.data.object;
        // metadata contains orderId if we set it
        const orderId = session.metadata && session.metadata.orderId;
        if (orderId) {
          await Order.findByIdAndUpdate(orderId, {
            'payment.status': 'paid',
            'payment.stripePaymentIntent': session.payment_intent || session.id,
            status: 'processing'
          });
        }
      }
      break;
    case 'payment_intent.payment_failed':
      {
        const intent = event.data.object;
        // qui si può aggiornare l'ordine attraverso metadata se presente
        console.warn('Payment failed', intent);
      }
      break;
    default:
      // console.log(`Unhandled event type ${event.type}`);
  }

  res.json({ received: true });
});

module.exports = router;


Aggiungi in src/index.js prima di app.use(express.json()) o gestisci solo per la route webhook come fatto:

const webhookRoutes = require('./routes/webhooks');
app.use('/api/webhooks', webhookRoutes);


Importante: express.raw() è necessario per la verifica della firma Stripe. Quando testi in locale con stripe-cli imposta il webhook secret.

7) Seed admin — src/utils/seedAdmin.js

Script che crea admin all'avvio se non esiste (opzionale).

const User = require('../models/User');

module.exports = async function seedAdmin() {
  try {
    const adminEmail = process.env.ADMIN_EMAIL;
    const adminPassword = process.env.ADMIN_PASSWORD;
    const adminName = process.env.ADMIN_NAME || 'Admin';

    if (!adminEmail || !adminPassword) {
      console.warn('ADMIN_EMAIL or ADMIN_PASSWORD not set; skipping admin seed');
      return;
    }

    const existing = await User.findOne({ email: adminEmail });
    if (existing) {
      console.log('Admin already exists');
      return;
    }

    const user = new User({
      name: adminName,
      email: adminEmail,
      password: adminPassword,
      role: 'admin'
    });
    await user.save();
    console.log('Admin user created:', adminEmail);
  } catch (err) {
    console.error('Error seeding admin:', err);
  }
};


Richiamalo in src/index.js subito dopo connectDB():

const seedAdmin = require('./utils/seedAdmin');
...
connectDB().then(() => seedAdmin());


(se hai connectDB sincrono, adegua la chiamata)


<html lang="it">
<head>
  <meta charset="UTF-8"></meta>
  <meta content="width=device-width, initial-scale=1.0" name="viewport"></meta>
  <title>Carrello - Negozio Online</title>
  <link href="style.css" rel="stylesheet"></link>
</head>
<body>
  <header>
    <div class="logo">🛍️ Il Mio Negozio</div>
    <nav>
      <ul>
        <li><a href="index.html">Home</a></li>
        <li><a href="shop.html">Prodotti</a></li>
        <li><a class="active" href="cart.html">Carrello 🛒</a></li>
        <li><a href="checkout.html">Checkout</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <h1>🛒 Il tuo Carrello</h1>
    <table id="cart-table">
      <thead>
        <tr>
          <th>Prodotto</th>
          <th>Prezzo</th>
          <th>Quantità</th>
          <th>Totale</th>
          <th>Azioni</th>
        </tr>
      </thead>
      <tbody id="cart-items">
        <!-- Qui vengono inseriti i prodotti dal JS -->
      </tbody>
    </table>

    <div class="cart-summary">
      <h3>Totale Carrello: &lt;

</h3></div></main></body></html>

Commenti

Post popolari in questo blog

Corso di Programmazione Strutturata e OOP: 3 Ereditarietà incapsulamento polimorfismo

Corso di CSS & Web Design: 3 – Box Model e spaziature

Corso di CSS & Web Design: 7 – Responsive Design