feat: stream route, admin subscriptions, fix CORS multi-origin
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 21s
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user