HEX
Server: Apache
System: Linux info 3.0 #1337 SMP Tue Jan 01 00:00:00 CEST 2000 all GNU/Linux
User: u115237990 (7145895)
PHP: 8.3.32
Disabled: NONE
Upload Files
File: /homepages/35/d993672390/htdocs/dermiteseborrheique/database/schema.sql
-- ════════════════════════════════════════════════════════════════
--  dermiteseborrheique.fr — Schéma MySQL complet
--  Moteur InnoDB, charset utf8mb4 (supporte emojis)
--  Version 1.0.0
-- ════════════════════════════════════════════════════════════════

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ─────────────────────────────────────────────────────────────────
--  UTILISATEURS
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS users (
    id              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36)        NOT NULL UNIQUE,           -- UUID public (dans les URLs)
    email           VARCHAR(255)    NOT NULL UNIQUE,
    email_verified  TINYINT(1)      NOT NULL DEFAULT 0,
    email_token     VARCHAR(128)    NULL,                      -- Token de vérification email
    name            VARCHAR(100)    NOT NULL,
    avatar_url      VARCHAR(500)    NULL,                      -- URL avatar (Google ou upload)
    password_hash   VARCHAR(255)    NULL,                      -- NULL si auth Google uniquement
    google_id       VARCHAR(100)    NULL UNIQUE,               -- ID Google OAuth
    plan            ENUM('free','essential','premium') NOT NULL DEFAULT 'free',
    plan_expires_at DATETIME        NULL,                      -- NULL = pas d'expiration (free)
    lang            VARCHAR(5)      NOT NULL DEFAULT 'fr',     -- Langue préférée
    timezone        VARCHAR(50)     NOT NULL DEFAULT 'Europe/Paris',
    is_admin        TINYINT(1)      NOT NULL DEFAULT 0,
    is_active       TINYINT(1)      NOT NULL DEFAULT 1,        -- Soft ban possible
    last_login_at   DATETIME        NULL,
    meta            JSON            NULL,                      -- Données extensibles futures
    created_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_email         (email),
    INDEX idx_google_id     (google_id),
    INDEX idx_plan          (plan),
    INDEX idx_created_at    (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  SESSIONS "REMEMBER ME"
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS remember_tokens (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NOT NULL,
    token_hash  VARCHAR(255)    NOT NULL UNIQUE,               -- Hash du cookie remember_me
    expires_at  DATETIME        NOT NULL,
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_token_hash (token_hash),
    INDEX idx_expires_at (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  PROTECTION BRUTE FORCE
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS login_attempts (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ip_address  VARCHAR(45)     NOT NULL,                      -- IPv4 et IPv6
    email       VARCHAR(255)    NULL,                          -- Email tenté (pour audit)
    attempted_at DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_ip_address    (ip_address),
    INDEX idx_attempted_at  (attempted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  TOKENS (reset password, vérification email, etc.)
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS tokens (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NOT NULL,
    type        ENUM('email_verify','password_reset','api_key') NOT NULL,
    token_hash  VARCHAR(255)    NOT NULL UNIQUE,
    expires_at  DATETIME        NOT NULL,
    used_at     DATETIME        NULL,                          -- NULL = pas encore utilisé
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_token_hash (token_hash),
    INDEX idx_type_user  (type, user_id),
    INDEX idx_expires_at (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  CATALOGUE D'ITEMS (aliments, produits, habitudes)
--  Table partagée — certains items sont globaux, d'autres privés
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS items (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NULL,                          -- NULL = item global/communautaire
    category    ENUM(
                    'food',         -- Aliment
                    'drink',        -- Boisson
                    'alcohol',      -- Alcool
                    'tobacco',      -- Tabac / cigarette
                    'cosmetic',     -- Produit cosmétique (crème, sérum...)
                    'haircare',     -- Soin capillaire (shampoing, après-shampoing...)
                    'medication',   -- Médicament / complément
                    'supplement',   -- Complément alimentaire
                    'other'         -- Autre
                ) NOT NULL DEFAULT 'food',
    name        VARCHAR(200)    NOT NULL,
    name_en     VARCHAR(200)    NULL,                          -- Nom anglais pour i18n
    brand       VARCHAR(100)    NULL,                          -- Marque (cosmétiques/produits)
    ingredients TEXT            NULL,                          -- Ingrédients (INCI pour cosmétiques)
    is_global   TINYINT(1)      NOT NULL DEFAULT 0,            -- 1 = visible par tous
    usage_count INT UNSIGNED    NOT NULL DEFAULT 0,            -- Popularité pour autocomplete
    meta        JSON            NULL,
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
    INDEX idx_category      (category),
    INDEX idx_name          (name),
    INDEX idx_is_global     (is_global),
    INDEX idx_usage_count   (usage_count DESC),
    FULLTEXT idx_ft_name    (name)                             -- Recherche fulltext pour autocomplete
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  ENTRÉES DE JOURNAL (une par user par jour)
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS journal_entries (
    id              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id         INT UNSIGNED    NOT NULL,
    entry_date      DATE            NOT NULL,                  -- Date de l'entrée (pas datetime)

    -- État de la peau
    skin_score      TINYINT UNSIGNED NOT NULL DEFAULT 3,       -- 1 (très mauvais) à 5 (excellent)
    is_crisis       TINYINT(1)      NOT NULL DEFAULT 0,        -- Crise déclarée ce jour
    crisis_severity TINYINT UNSIGNED NULL,                     -- 1-3 si is_crisis = 1

    -- Zones touchées (stockées en JSON pour flexibilité)
    skin_zones      JSON            NULL,
    -- Exemple : {"scalp": 2, "face": 1, "eyebrows": 3, "nose": 0, "chest": 0, "ears": 1}
    -- 0 = pas touché, 1 = léger, 2 = modéré, 3 = sévère

    -- Photo de peau (optionnel, premium)
    photo_path      VARCHAR(500)    NULL,

    -- Facteurs lifestyle
    sleep_hours     DECIMAL(3,1)    NULL,                      -- Heures de sommeil
    sleep_quality   TINYINT UNSIGNED NULL,                     -- 1-5
    stress_level    TINYINT UNSIGNED NULL,                     -- 1-5
    exercise_mins   SMALLINT UNSIGNED NULL,                    -- Minutes d'exercice
    water_liters    DECIMAL(3,1)    NULL,                      -- Litres d'eau

    -- Notes libres
    notes           TEXT            NULL,

    -- Données extensibles
    meta            JSON            NULL,

    created_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uq_user_date (user_id, entry_date),             -- Une seule entrée par user par jour
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_date     (user_id, entry_date DESC),
    INDEX idx_skin_score    (user_id, skin_score),
    INDEX idx_is_crisis     (user_id, is_crisis)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  ITEMS D'UNE ENTRÉE (pivot journal ↔ items)
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS entry_items (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    entry_id    INT UNSIGNED    NOT NULL,
    item_id     INT UNSIGNED    NOT NULL,
    quantity    VARCHAR(50)     NULL,                          -- "2 verres", "1 portion"
    meal_time   ENUM('morning','noon','afternoon','evening','night') NULL,
    notes       VARCHAR(255)    NULL,
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (entry_id) REFERENCES journal_entries(id) ON DELETE CASCADE,
    FOREIGN KEY (item_id)  REFERENCES items(id) ON DELETE RESTRICT,
    INDEX idx_entry_id  (entry_id),
    INDEX idx_item_id   (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  RÉSULTATS DE CORRÉLATION (calculés périodiquement)
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS correlations (
    id                  INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id             INT UNSIGNED    NOT NULL,
    item_id             INT UNSIGNED    NOT NULL,

    -- Scores de corrélation
    score               DECIMAL(5,2)    NOT NULL DEFAULT 0,    -- 0.00 à 100.00
    occurrences_total   SMALLINT UNSIGNED NOT NULL DEFAULT 0,  -- Nb de fois consommé
    occurrences_crisis  SMALLINT UNSIGNED NOT NULL DEFAULT 0,  -- Nb de fois avant crise
    time_window_hours   TINYINT UNSIGNED  NOT NULL DEFAULT 48, -- Fenêtre utilisée (12/24/48/72h)

    -- Statut
    status              ENUM('suspect','cleared','ignored','confirmed') NOT NULL DEFAULT 'suspect',
    -- cleared = testé et éliminé sans amélioration
    -- confirmed = user confirme que c'est un déclencheur
    -- ignored = user veut ignorer cet item

    last_computed_at    DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at          DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at          DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uq_user_item_window (user_id, item_id, time_window_hours),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
    INDEX idx_user_score    (user_id, score DESC),
    INDEX idx_status        (user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  OBJECTIFS D'ÉLIMINATION
--  "Je teste sans gluten pendant 3 semaines"
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS elimination_goals (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NOT NULL,
    item_id     INT UNSIGNED    NOT NULL,
    start_date  DATE            NOT NULL,
    end_date    DATE            NOT NULL,
    status      ENUM('active','completed','abandoned') NOT NULL DEFAULT 'active',
    outcome     ENUM('improved','no_change','worse','unknown') NULL, -- Résultat après test
    notes       TEXT            NULL,
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
    INDEX idx_user_status (user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  STATISTIQUES COMMUNAUTAIRES (agrégées anonymisées)
--  Recalculées périodiquement par un cron
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS community_stats (
    id              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    item_id         INT UNSIGNED    NOT NULL UNIQUE,
    total_users     INT UNSIGNED    NOT NULL DEFAULT 0,        -- Users ayant consommé cet item
    crisis_users    INT UNSIGNED    NOT NULL DEFAULT 0,        -- Users ayant eu une crise après
    avg_score       DECIMAL(5,2)    NOT NULL DEFAULT 0,        -- Score moyen de suspicion
    computed_at     DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
    INDEX idx_avg_score (avg_score DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  GAMIFICATION — BADGES
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS badges (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    slug        VARCHAR(50)     NOT NULL UNIQUE,               -- Ex: "streak_7", "first_suspect"
    name        VARCHAR(100)    NOT NULL,
    name_en     VARCHAR(100)    NULL,
    description TEXT            NULL,
    icon        VARCHAR(10)     NOT NULL DEFAULT '🏆',         -- Emoji
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS user_badges (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NOT NULL,
    badge_id    INT UNSIGNED    NOT NULL,
    earned_at   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uq_user_badge (user_id, badge_id),
    FOREIGN KEY (user_id)  REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (badge_id) REFERENCES badges(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  ABONNEMENTS / PAIEMENTS (préparation Stripe)
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS subscriptions (
    id                  INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id             INT UNSIGNED    NOT NULL,
    plan                ENUM('essential','premium') NOT NULL,
    status              ENUM('active','cancelled','expired','trial') NOT NULL DEFAULT 'trial',
    stripe_customer_id  VARCHAR(100)    NULL,
    stripe_sub_id       VARCHAR(100)    NULL,
    current_period_start DATETIME       NULL,
    current_period_end   DATETIME       NULL,
    cancelled_at        DATETIME        NULL,
    meta                JSON            NULL,
    created_at          DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at          DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_status       (user_id, status),
    INDEX idx_stripe_customer   (stripe_customer_id),
    INDEX idx_stripe_sub        (stripe_sub_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  NOTIFICATIONS
-- ─────────────────────────────────────────────────────────────────

CREATE TABLE IF NOT EXISTS notifications (
    id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     INT UNSIGNED    NOT NULL,
    type        VARCHAR(50)     NOT NULL,                      -- "new_suspect", "streak", "reminder"...
    title       VARCHAR(200)    NOT NULL,
    body        TEXT            NULL,
    is_read     TINYINT(1)      NOT NULL DEFAULT 0,
    meta        JSON            NULL,                          -- Données contextuelles (item_id, etc.)
    created_at  DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_read     (user_id, is_read),
    INDEX idx_created_at    (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ─────────────────────────────────────────────────────────────────
--  DONNÉES INITIALES — Badges
-- ─────────────────────────────────────────────────────────────────

INSERT IGNORE INTO badges (slug, name, name_en, description, icon) VALUES
('first_entry',    'Premier journal',       'First Entry',       'Première entrée de journal complétée', '📔'),
('streak_7',       '7 jours consécutifs',   '7-day streak',      'Journal complété 7 jours de suite',    '🔥'),
('streak_30',      '30 jours consécutifs',  '30-day streak',     'Journal complété 30 jours de suite',   '⚡'),
('streak_90',      '90 jours consécutifs',  '90-day streak',     'Journal complété 90 jours de suite',   '💎'),
('first_suspect',  'Premier suspect',       'First Suspect',     'Premier déclencheur identifié',        '🔍'),
('confirmed_trigger', 'Déclencheur confirmé', 'Confirmed Trigger', 'Un déclencheur confirmé après test', '✅'),
('first_elim',     'Test d\'élimination',   'Elimination Test',  'Premier objectif d\'élimination lancé','🧪'),
('pdf_export',     'Rapport généré',        'Report Generated',  'Premier rapport PDF exporté',          '📄'),
('community',      'Membre actif',          'Active Member',     '30 jours d\'utilisation',              '🌍');

-- ─────────────────────────────────────────────────────────────────
--  DONNÉES INITIALES — Items globaux les plus courants
-- ─────────────────────────────────────────────────────────────────

INSERT IGNORE INTO items (user_id, category, name, name_en, is_global) VALUES
-- Aliments suspects connus
(NULL, 'food',    'Pain au levain',         'Sourdough bread',       1),
(NULL, 'food',    'Pain blanc',             'White bread',           1),
(NULL, 'food',    'Fromage affiné',         'Aged cheese',           1),
(NULL, 'food',    'Yaourt nature',          'Plain yogurt',          1),
(NULL, 'food',    'Lait de vache',          'Cow milk',              1),
(NULL, 'food',    'Gluten (général)',        'Gluten (general)',      1),
(NULL, 'food',    'Sucre raffiné',          'Refined sugar',         1),
(NULL, 'food',    'Chocolat noir',          'Dark chocolate',        1),
(NULL, 'food',    'Noix',                   'Nuts',                  1),
(NULL, 'food',    'Avocat',                 'Avocado',               1),
(NULL, 'food',    'Œufs',                   'Eggs',                  1),
(NULL, 'food',    'Poisson gras',           'Fatty fish',            1),
-- Boissons
(NULL, 'drink',   'Eau plate',              'Still water',           1),
(NULL, 'drink',   'Café',                   'Coffee',                1),
(NULL, 'drink',   'Thé vert',               'Green tea',             1),
(NULL, 'drink',   'Jus de fruit',           'Fruit juice',           1),
-- Alcool
(NULL, 'alcohol', 'Vin rouge',              'Red wine',              1),
(NULL, 'alcohol', 'Vin blanc',              'White wine',            1),
(NULL, 'alcohol', 'Bière',                  'Beer',                  1),
(NULL, 'alcohol', 'Spiritueux',             'Spirits',               1),
-- Tabac
(NULL, 'tobacco', 'Cigarette',              'Cigarette',             1),
(NULL, 'tobacco', 'Cigarette électronique', 'E-cigarette',           1),
-- Soins capillaires (suspects courants)
(NULL, 'haircare','Shampooing ordinaire',    'Regular shampoo',       1),
(NULL, 'haircare','Shampooing antipelliculaire', 'Anti-dandruff shampoo', 1),
(NULL, 'haircare','Après-shampooing',        'Conditioner',           1),
(NULL, 'haircare','Gel coiffant',            'Hair gel',              1),
(NULL, 'haircare','Huile capillaire',        'Hair oil',              1),
-- Cosmétiques (ingrédients suspects)
(NULL, 'cosmetic','Crème hydratante',        'Moisturizer',           1),
(NULL, 'cosmetic','Huile de coco',           'Coconut oil',           1),
(NULL, 'cosmetic','Gel douche',              'Shower gel',            1),
(NULL, 'cosmetic','Savon ordinaire',         'Regular soap',          1),
(NULL, 'cosmetic','Crème solaire',           'Sunscreen',             1);

SET FOREIGN_KEY_CHECKS = 1;