feat: stream route, admin subscriptions, fix CORS multi-origin
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 21s

- index.ts : CORS supporte plusieurs origines (FRONTEND_URL séparé par virgule)
- stream.routes.ts : GET /api/stream/:key* — sert fichiers locaux avec auth
  optionnelle, contrôle d'accès par level, support Range requests (seekable)
- admin.routes.ts : POST /api/admin/users/:id/subscriptions — assigne un plan,
  expire l'abonnement actif précédent
- Fix .env VPS : FRONTEND_URL=origins.tetardtek.com (domaine correct)
This commit is contained in:
2026-03-14 09:58:01 +01:00
parent 4265d21c8b
commit 666cf6a435
3 changed files with 202 additions and 1 deletions

View File

@@ -342,4 +342,59 @@ router.patch("/plans/:id", async (req: Request, res: Response): Promise<void> =>
}
});
// ---------------------------------------------------------------------------
// USER SUBSCRIPTIONS
// ---------------------------------------------------------------------------
/**
* POST /api/admin/users/:id/subscriptions
* Assigne un plan à un utilisateur.
* Expire l'abonnement actif précédent si existant.
* Body: { planId, endsAt? } — endsAt ISO string, null = permanent
*/
router.post("/users/:id/subscriptions", async (req: Request, res: Response): Promise<void> => {
const { planId, endsAt = null } = req.body as { planId?: string; endsAt?: string | null };
if (!planId) {
res.status(400).json({ success: false, error: "MISSING_PLAN_ID" });
return;
}
try {
const userRepo = AppDataSource.getRepository(User);
const planRepo = AppDataSource.getRepository(SubscriptionPlan);
const subRepo = AppDataSource.getRepository(UserSubscription);
const [user, plan] = await Promise.all([
userRepo.findOne({ where: { id: req.params.id } }),
planRepo.findOne({ where: { id: planId } }),
]);
if (!user) { res.status(404).json({ success: false, error: "USER_NOT_FOUND" }); return; }
if (!plan) { res.status(404).json({ success: false, error: "PLAN_NOT_FOUND" }); return; }
// Expirer l'abonnement actif précédent
await subRepo
.createQueryBuilder()
.update(UserSubscription)
.set({ status: "expired" })
.where("userId = :userId AND status = :status", { userId: user.id, status: "active" })
.execute();
const sub = subRepo.create({
id: crypto.randomUUID(),
userId: user.id,
planId: plan.id,
status: "active",
startsAt: new Date(),
endsAt: endsAt ? new Date(endsAt) : null,
});
await subRepo.save(sub);
res.status(201).json({ success: true, data: { subscription: { ...sub, plan } } });
} catch {
res.status(500).json({ success: false, error: "INTERNAL_ERROR" });
}
});
export default router;