ScaleRocket/Web

Base de données

Schéma PostgreSQL Supabase, tables, politiques RLS, triggers et modèles de migration.

Vue d'ensemble

ScaleRocket utilise une base de données pour stocker les utilisateurs, les abonnements et les crédits. Le schéma et l'approche de migration diffèrent selon votre backend.

ScaleRocket utilise Supabase (PostgreSQL) comme base de données. Le schéma se trouve dans supabase/migrations/ et est versionné. Chaque table a le Row Level Security (RLS) activé par défaut.

ScaleRocket utilise Convex comme base de données. Le schéma est défini en TypeScript dans convex/schema.ts et est automatiquement synchronisé lorsque vous exécutez npx convex dev. Convex gère le contrôle d'accès via l'autorisation au niveau des fonctions.

Tables

profiles

Créée automatiquement lorsqu'un utilisateur s'inscrit via un trigger de base de données.

ColonneTypeDescription
iduuidRéférence auth.users.id
emailtextEmail de l'utilisateur
full_nametextNom d'affichage
avatar_urltextURL de la photo de profil
created_attimestamptzDate de création du compte
updated_attimestamptzDernière mise à jour du profil

subscriptions

Suit l'état de l'abonnement Stripe pour chaque utilisateur.

ColonneTypeDescription
iduuidClé primaire
user_iduuidRéférence profiles.id
stripe_customer_idtextIdentifiant client Stripe
stripe_subscription_idtextIdentifiant d'abonnement Stripe
plan_idtextIdentifiant du plan
statustextactive, canceled, past_due, etc.
current_period_endtimestamptzFin de la période de facturation en cours
created_attimestamptzDate de création de l'abonnement
updated_attimestamptzDernière mise à jour

credits

Stocke le solde de crédits pour chaque utilisateur.

ColonneTypeDescription
iduuidClé primaire
user_iduuidRéférence profiles.id
balanceintegerSolde de crédits actuel
monthly_allowanceintegerCrédits accordés par cycle de facturation
last_reset_attimestamptzDate de la dernière réinitialisation mensuelle
updated_attimestamptzDernière modification

Définition du schéma

Le schéma est défini via des fichiers de migration SQL dans supabase/migrations/. Chaque migration est un fichier .sql exécuté séquentiellement.

Le schéma est défini en TypeScript dans convex/schema.ts :

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  profiles: defineTable({
    userId: v.string(),
    email: v.string(),
    fullName: v.optional(v.string()),
    avatarUrl: v.optional(v.string()),
  }).index("by_userId", ["userId"]),

  subscriptions: defineTable({
    userId: v.string(),
    stripeCustomerId: v.string(),
    stripeSubscriptionId: v.string(),
    planId: v.string(),
    status: v.string(),
    currentPeriodEnd: v.number(),
  }).index("by_userId", ["userId"])
    .index("by_stripeCustomerId", ["stripeCustomerId"]),

  credits: defineTable({
    userId: v.string(),
    balance: v.number(),
    monthlyAllowance: v.number(),
    lastResetAt: v.number(),
  }).index("by_userId", ["userId"]),
});

Les modifications du schéma sont appliquées automatiquement lorsque vous exécutez npx convex dev.

Row Level Security (RLS)

Chaque table a le RLS activé. Les utilisateurs ne peuvent accéder qu'à leurs propres données :

-- profiles: users can read and update their own profile
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id);

-- subscriptions: users can only read their own subscription
CREATE POLICY "Users can view own subscription"
  ON subscriptions FOR SELECT
  USING (auth.uid() = user_id);

-- credits: users can only read their own credits
CREATE POLICY "Users can view own credits"
  ON credits FOR SELECT
  USING (auth.uid() = user_id);

Le service role (utilisé par les Edge Functions et le panneau d'administration) contourne entièrement le RLS.

Convex n'utilise pas le RLS. Le contrôle d'accès est appliqué au niveau des fonctions. Chaque query ou mutation vérifie l'identité de l'utilisateur :

// convex/profiles.ts
import { query } from "./_generated/server";
import { getAuthUserId } from "@convex-dev/auth/server";

export const getProfile = query({
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    return await ctx.db
      .query("profiles")
      .withIndex("by_userId", (q) => q.eq("userId", userId))
      .unique();
  },
});

Triggers

Création automatique du profil à l'inscription

CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
  INSERT INTO public.profiles (id, email, full_name, avatar_url)
  VALUES (
    NEW.id,
    NEW.email,
    NEW.raw_user_meta_data->>'full_name',
    NEW.raw_user_meta_data->>'avatar_url'
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

Avec Convex, la création du profil est gérée dans le callback d'authentification ou une mutation déclenchée après l'inscription :

// convex/users.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";

export const createProfile = internalMutation({
  args: { userId: v.string(), email: v.string(), fullName: v.optional(v.string()) },
  handler: async (ctx, args) => {
    await ctx.db.insert("profiles", {
      userId: args.userId,
      email: args.email,
      fullName: args.fullName,
    });
  },
});

Mise à jour automatique de updated_at

CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS trigger AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Applied to all tables
CREATE TRIGGER set_updated_at
  BEFORE UPDATE ON profiles
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

Index

CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id);
CREATE INDEX idx_subscriptions_stripe_customer_id ON subscriptions(stripe_customer_id);
CREATE INDEX idx_credits_user_id ON credits(user_id);

Ajouter de nouvelles tables

  1. Créez une nouvelle migration :
pnpm supabase migration new add_my_table
  1. Modifiez le fichier généré dans supabase/migrations/ :
CREATE TABLE my_table (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
  content text,
  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);

ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can CRUD own rows"
  ON my_table FOR ALL
  USING (auth.uid() = user_id);

CREATE TRIGGER set_updated_at
  BEFORE UPDATE ON my_table
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
  1. Appliquez la migration :
pnpm supabase db push
  1. Ajoutez la table dans convex/schema.ts :
// convex/schema.ts
export default defineSchema({
  // ... tables existantes
  myTable: defineTable({
    userId: v.string(),
    content: v.optional(v.string()),
  }).index("by_userId", ["userId"]),
});
  1. Le schéma est appliqué automatiquement lorsque vous exécutez :
npx convex dev

Pas besoin de fichiers de migration -- Convex gère les changements de schéma automatiquement.

Génération de types

Générez les types TypeScript à partir du schéma de votre base de données :

pnpm supabase gen types typescript --local > packages/types/src/database.ts

Cela met à jour le type Database utilisé dans toutes les applications. Exécutez cette commande après chaque migration.

Convex génère automatiquement les types TypeScript à partir de convex/schema.ts. Les types sont disponibles via le répertoire _generated et sont mis à jour en temps réel lorsque vous exécutez npx convex dev.

Fini ? Marquez cette page comme terminée.

On this page