All files / src/routes/api/v1 ssl.js

0% Statements 0/46
0% Branches 0/24
0% Functions 0/4
0% Lines 0/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128                                                                                                                                                                                                                                                               
/**
 * API v1 — SSL Certificats (read-only)
 *
 * GET /api/v1/ssl       — Liste des certificats surveilles
 * GET /api/v1/ssl/:id   — Detail d'un certificat
 *
 * @module routes/api/v1/ssl
 */
 
const express = require('express');
const { pool: db } = require('../../../db');
const { requireScope } = require('../../../middleware/apiAuth');
const { checkQuota } = require('../../../middleware/planLimits');
const { apiSuccess, apiPaginated, apiError, parsePagination, makeApiDeleteHandler } = require('../../../helpers/apiResponse');
 
const DOMAIN_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/;
 
const router = express.Router();
 
// ─── GET /ssl ───────────────────────────────────────────────────────────────
 
router.get('/', requireScope('read'), async (req, res, next) => {
  try {
    const { page, perPage, offset } = parsePagination(req.query);
 
    const [{ rows }, { rows: countRows }] = await Promise.all([
      db.query(
        `SELECT id, domain, port, issuer, subject, valid_from, valid_to,
                alert_days, enabled, last_check, last_status, last_error, created_at
         FROM ssl_certificates
         WHERE user_id = $1
         ORDER BY created_at
         LIMIT $2 OFFSET $3`,
        [req.user.id, perPage, offset],
      ),
      db.query('SELECT COUNT(*) FROM ssl_certificates WHERE user_id = $1', [req.user.id]),
    ]);
 
    // Calculer days_remaining
    const now = new Date();
    const data = rows.map(cert => ({
      ...cert,
      days_remaining: cert.valid_to
        ? Math.ceil((new Date(cert.valid_to) - now) / (1000 * 60 * 60 * 24))
        : null,
    }));
 
    apiPaginated(res, data, { page, per_page: perPage, total: Number.parseInt(countRows[0].count) });
  } catch (err) {
    next(err);
  }
});
 
// ─── GET /ssl/:id ───────────────────────────────────────────────────────────
 
router.get('/:id', requireScope('read'), async (req, res, next) => {
  try {
    const { rows } = await db.query(
      `SELECT id, domain, port, issuer, subject, valid_from, valid_to,
              alert_days, enabled, last_check, last_status, last_error, created_at
       FROM ssl_certificates
       WHERE id = $1 AND user_id = $2`,
      [req.params.id, req.user.id],
    );
 
    if (rows.length === 0) {
      return apiError(res, 404, 'NOT_FOUND', 'Certificat introuvable.');
    }
 
    const cert = rows[0];
    cert.days_remaining = cert.valid_to
      ? Math.ceil((new Date(cert.valid_to) - Date.now()) / (1000 * 60 * 60 * 24))
      : null;
 
    apiSuccess(res, cert);
  } catch (err) {
    next(err);
  }
});
 
// ─── POST /ssl ──────────────────────────────────────────────────────────────
 
router.post('/', requireScope('write'), checkQuota('ssl_certificates', 'ssl_certificates'), async (req, res, next) => {
  try {
    const { domain, port = 443, alert_days = 30 } = req.body;
    if (!domain) {
      return apiError(res, 400, 'VALIDATION_ERROR', 'domain est requis.');
    }
    if (domain.length > 253 || !DOMAIN_REGEX.test(domain)) {
      return apiError(res, 400, 'VALIDATION_ERROR', 'Nom de domaine invalide.');
    }
    const p = Number.parseInt(port);
    if (p < 1 || p > 65535) {
      return apiError(res, 400, 'VALIDATION_ERROR', 'Port invalide (1-65535).');
    }
    const ad = Number.parseInt(alert_days);
    if (ad < 1 || ad > 365) {
      return apiError(res, 400, 'VALIDATION_ERROR', 'alert_days invalide (1-365).');
    }
 
    // Doublon
    const { rows: dup } = await db.query(
      'SELECT id FROM ssl_certificates WHERE user_id = $1 AND domain = $2 AND port = $3',
      [req.user.id, domain, p],
    );
    if (dup.length > 0) {
      return apiError(res, 409, 'DUPLICATE', 'Ce domaine:port est deja surveille.');
    }
 
    const { rows } = await db.query(
      `INSERT INTO ssl_certificates (user_id, domain, port, alert_days)
       VALUES ($1, $2, $3, $4)
       RETURNING id, domain, port, alert_days, enabled, last_status, created_at`,
      [req.user.id, domain, p, ad],
    );
 
    apiSuccess(res, rows[0], 201);
  } catch (err) {
    next(err);
  }
});
 
// ─── DELETE /ssl/:id ────────────────────────────────────────────────────────
 
router.delete('/:id', requireScope('write'), makeApiDeleteHandler('ssl_certificates', 'Certificat introuvable.'));
 
module.exports = router;