尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Tracker de distribution pour les carrousels TikTok et Instagram de RepackIt. Permet de centraliser, suivre et orchestrer la diffusion de contenus carrousel sur les deux plateformes.
next@latest (équivalent fonctionnel à Next.js 15 demandé : App Router, Server Components, Server Actions)@tailwindcss/postcss)base-novabutton, input, select, dropdown-menu, dialog, table, card, badge, tabs, label, textarea, sonner (remplace toast, déprécié dans le registry shadcn), calendar, popover.
Ce projet est l'outil interne de RepackIt pour suivre la distribution des carrousels (TikTok + Instagram) : programmation, statut de publication, métriques par plateforme, et reporting cross-canal.
pnpm dev # Lance le dev server sur http://localhost:3000
pnpm build # Build production
pnpm start # Lance le build en mode production
pnpm lint # Lint le code (ESLint + config Next.js)
app/ # App Router (pages, layouts, route handlers)
components/ui/ # Composants shadcn/ui
lib/ # Utilitaires (cn, etc.)
public/ # Assets statiques
Le build Vercel pousse le backend Convex dans le même build que le front :
vercel.json (commité) appelle npx convex deploy --cmd 'pnpm build && …'.
convex deploy lance d'abord --cmd (le next build), puis pousse Convex,
et seulement si --cmd a réussi.
Garde de tête (scripts/convex-head-guard.mjs) : deux merges rapprochés
lancent deux builds Production en parallèle, et le dernier à pousser Convex
gagne, même s'il porte le commit le plus ancien (vu le 2026-10-29). Le garde
tourne avant le build, puis en dernière commande du --cmd, juste avant le
push. Un build de production dont le commit n'est plus la tête de main échoue
sans pousser ; le build de la tête s'en charge. Une tête illisible
(git ls-remote anonyme : le dépôt doit rester public) fait aussi échouer le
build.
Configuration une seule fois :
repackit-distribution-tracker → deployment prod
fiery-wolf-460 → Settings → Generate Production Deploy Key.CONVEX_DEPLOY_KEY (scope Production) avec la clé générée.Sans CONVEX_DEPLOY_KEY, le build Vercel échoue — c'est voulu : on ne peut
plus shipper du code sans son schéma.
Toutes les tables métier sont scopées par projectId (tables projects +
memberships). Numérotation C###/S###/SR### par projet.
À cause du déploiement atomique (le convex deploy du build pousse le schéma
AVANT toute migration data), le rollout a été fait en 2 phases (prod
backfillée le 2026-06-12) :
Phase 1 (feat(multi-tenant)) — projectId en v.optional + tables
projects/memberships + indexes. Après déploiement, lancé une fois
sur prod :
./node_modules/.bin/convex run migrations:setupRepackitProject --prod
→ projet repackit, membership admin du superadmin, backfill projectId
partout, unset vuesJ1/J3/J7 (TD-016). Comptages des 9 tables inchangés
(64 publications, 490 hooks, etc.), KPIs dashboard identiques.
Phase 2 (chore(multi-tenant)) — la prod étant backfillée, projectId
resserré en v.id("projects") (non-optional) sur les 9 tables et
vuesJ1/J3/J7 retirés du schéma. Le succès du convex deploy au build
prouve qu'aucun doc prod ne viole le schéma resserré.
TD-017 (comptes.actif) reste différé : lib/compte-status.ts + ~12 specs e2e
le lisent encore.
Pour amorcer un nouveau deployment (dev/test/prod neuf), lancer
setupRepackitProject après le 1er provisioning auth (crée le projet repackit
/admin/[projectSlug] + switcher + création de projetL'app interne vit sous /admin/[projectSlug]/… (dashboard, comptes, carrousels,
shorts, screenrecorder, biblio-hooks, inspirations). L'URL est la source de
vérité du projet actif : ProjectProvider lit le segment [projectSlug] et
le résout via api.projects.getProjectForCurrentUser (vérif superadmin ou
membership ; 404 propre si slug inexistant, accès refusé sinon). Plus aucun
projet implicite ni stocké en localStorage.
/ résout le projet par défaut de l'utilisateur (getCurrentProject) puis
redirige vers /admin/<slug>/dashboard. Les anciennes routes (/dashboard,
/carrousels?carouselId=…, /nouveau, /tracker…) redirigent vers
/admin/repackit/… (slug par défaut). /p/[carouselId] est un resolver
cross-projet : il cherche le carouselId dans les projets accessibles puis
redirige vers la route scopée (404 sinon).api.projects.listMyProjects :
superadmin → tous, sinon memberships). « Créer un projet » (superadmin,
api.projects.createProject) : modal nom + slug + couleur d'accent (#FF5200)
api.projects.getMe) + déconnexion.tracker.snapshot-age:<slug> (période J+X par projet) ;
sidebar-collapsed et le schéma des filterPresets restent globaux./admin/e2e-test/… (helper adminPath) sur un projet
dédié e2e-test (créé par auth.setup.ts).Reste différé (hors P3) : rôle creator + portail /app, theming par
accentColor.
L'app est protégée de bout en bout :
convex/functions.ts. C'est LA barrière de sécurité —
NEXT_PUBLIC_CONVEX_URL est publique dans le bundle, protéger les pages ne
suffit pas. Couches (de la plus large à la plus stricte) :
authedQuery / authedMutation : session requise (ctx.userId) ;superadminMutation : rôle global superadmin ;projectQuery / projectMutation : accès au projet (membership ou
superadmin), tout rôle — ctx.projectId injecté ;adminQuery / adminMutation : membership admin (ou superadmin) sur le
projet. Toute l'app interne passe dessus → le rôle creator n'y a
aucun accès ;creatorQuery / creatorMutation : membership creator, résout SA fiche
creators et injecte ctx.creatorId ; la donnée servie est filtrée par
ce creatorId (portail /app) ;publicQuery : pré-session, réservé à l'aperçu d'invitation (/join).proxy.ts (middleware Next 16) redirige tout visiteur
sans session vers /login (sauf routes publiques /login et /join/...).
Routage par rôle : un creator sur /admin/* est renvoyé vers /app, un
admin/superadmin sur /app vers son /admin.users vide → rôle
superadmin). Ensuite, création de comptes uniquement par invitation :
un admin invite un créateur (/admin/[slug]/createurs) → lien /join/<token>
(uuid, +14 j, usage unique). Le signUp n'est autorisé que par un token
valide (convex/auth.ts createOrUpdateUser) : il crée le user (rôle
member), le membership creator, lie la fiche et marque l'invitation
consommée — le tout atomiquement. Token absent/expiré/réutilisé → rejet sans
leak.# 1. Clés JWT (affiche les deux commandes `convex env set` à exécuter)
pnpm tsx scripts/generate-jwt-keys.ts
# 2. URL du site (redirections auth)
./node_modules/.bin/convex env set SITE_URL http://localhost:3000 # prod : URL Vercel, avec --prod
# 3. Secret e2e — deployments de DEV/TEST UNIQUEMENT, JAMAIS en prod
./node_modules/.bin/convex env set E2E_SECRET <valeur ≥ 8 caractères>
# 3 bis. JWT longue durée — DEV/TEST UNIQUEMENT, JAMAIS en prod (prod = 1h).
# Défense en profondeur du fixture d'auth e2e : évite tout refresh de token
# pendant un test (les refresh tokens sont single-use → races sinon). 30 jours.
./node_modules/.bin/convex env set JWT_DURATION_MS 2592000000
# 4. Pousser schéma + fonctions, puis créer le compte initial sur /login
./node_modules/.bin/convex dev --once # prod : convex deploy (ou push Vercel)
Premier compte : ouvrir /login → « Créer le compte initial » → superadmin.
Compte perdu ou E2E_SECRET changé après création du user e2e :
./node_modules/.bin/convex run maintenance:wipeAuthTables rouvre la fenêtre
bootstrap (purge users + sessions).
Ces deux-là coûtent une heure chacun si on ne les connaît pas.
1. JWKS s'écrit en JSON BRUT, jamais requoté. Une valeur double-encodée
(JWKS="{\"keys\":…}" au lieu de JWKS={"keys":…}) fait servir au déploiement
un document de découverte invalide, et le backend répond :
AuthProviderDiscoveryFailed: Auth provider discovery of http://… failed
Error discovering auth provider: …, Failed to parse server response
Le message accuse la découverte réseau alors que la cause est l'encodage : les endpoints répondent bien 200, c'est leur contenu qui n'est pas du JSON.
2. Ne passe JAMAIS la clé privée en ARGUMENT.
convex env set JWT_PRIVATE_KEY -- "-----BEGIN PRIVATE KEY----- …" : le CLI
interprète -----BEGIN comme une option, échoue avec
error: unknown option '-----BEGIN PRIVATE KEY----- …' — et écho la clé
privée en clair dans ce message. Toujours passer par un fichier :
convex env set --from-file <fichier.env> # valeurs jamais en argv
Le user e2e ([email protected], mot de passe = E2E_SECRET) est créé au
premier run par e2e/auth.setup.ts via la fenêtre bootstrap ; sa session
(storageState) est partagée par toutes les specs.
Backend LOCAL (chemin par défaut, local ET CI) — la suite e2e ne dépend plus d'un déploiement cloud :
./scripts/convex-local.sh start # backend + schéma + auth (~4 s)
eval "$(./scripts/convex-local.sh env)" && pnpm test:e2e
./scripts/convex-local.sh reset # base VIERGE (état résiduel)
./scripts/convex-local.sh stop
Backend Convex open-source, SQLite, base neuve par run. Le tag du binaire
est épinglé dans le script (upstream ne publie aucune matrice de
compatibilité CLI ↔ backend, et l'image Docker n'existe qu'en :latest) : le
monter est une décision délibérée, à valider en relançant la suite entière.
Tout l'état vit dans .convex-local/ (gitignoré) ; .env.local n'est jamais
touché, les variables self-hosted passent par --env-file. Mélanger cloud et
self-hosted dans un même fichier est refusé par Convex.
En CI : aucun secret GitHub n'est nécessaire (secret d'instance, clé admin,
clés JWT et E2E_SECRET sont générés par run). Deux PR peuvent tester en
parallèle : chacune a son backend.
Contre le cloud (dépannage ponctuel) : exporter NEXT_PUBLIC_CONVEX_URL +
E2E_SECRET vers le deployment visé, qui doit être provisionné avec les
fonctions à jour. À réserver au diagnostic d'une divergence backend.
Re-seed des hooks en prod (exceptionnel) : définir temporairement
E2E_SECRET sur la prod via convex env set --prod, lancer
pnpm tsx scripts/run-seed.ts --env prod, puis retirer la variable.
Smoke prod (scripts/smoke-prod-presets.ts) : s'authentifie avec un vrai
compte prod via PROD_SMOKE_EMAIL / PROD_SMOKE_PASSWORD
(.env.prod.local).
seedHooksclearHookscleanupTest*wipeAllPublicationssecretE2E_SECRET