Spaces:
Running
Running
Use plan-period session usage counter
Browse files- src/admin.ts +6 -6
- src/analysis.ts +2 -2
- src/server.ts +3 -4
src/admin.ts
CHANGED
|
@@ -64,7 +64,7 @@ export function registerAdminRoutes(app: Express) {
|
|
| 64 |
if (error) throw new Error(`Unable to load user directory: ${error.message}`);
|
| 65 |
const ids = (data ?? []).map((row) => row.user_id);
|
| 66 |
const [subs, counts] = ids.length ? await Promise.all([
|
| 67 |
-
supabaseAdmin.from("users_subscriptions").select("user_id,subscription_id,startdate,enddate,num_players,num_sessions
|
| 68 |
supabaseAdmin.from("sessions").select("user_id").in("user_id", ids),
|
| 69 |
]) : [{ data: [], error: null }, { data: [], error: null }];
|
| 70 |
if (subs.error) throw new Error(`Unable to load user plans: ${subs.error.message}`);
|
|
@@ -85,7 +85,7 @@ export function registerAdminRoutes(app: Express) {
|
|
| 85 |
id: row.user_id, fname: row.fname, lname: row.lname, name: `${row.fname} ${row.lname}`.trim(),
|
| 86 |
email: row.email, academy: row.academy, role: row.role, imagePath: row.image_path, accountRole: row.account_role,
|
| 87 |
subscription: plan ? { id: plan.subscription_id, name: plan.plan, startDate: assignment?.startdate, endDate: assignment?.enddate, playerLimit: plan.player_limit, sessionLimit: plan.monthly_session_limit } : null,
|
| 88 |
-
usage: { players: assignment?.num_players ?? 0, sessions: assignment?.
|
| 89 |
};
|
| 90 |
}) });
|
| 91 |
}));
|
|
@@ -102,7 +102,7 @@ export function registerAdminRoutes(app: Express) {
|
|
| 102 |
const id = uuid.safeParse(req.params.userId); if (!id.success) return res.sendStatus(404);
|
| 103 |
const [{ data: user, error }, { data: assignment, error: subError }] = await Promise.all([
|
| 104 |
supabaseAdmin.from("users").select("user_id,fname,lname,email,academy,role,image_path,account_role").eq("user_id", id.data).maybeSingle(),
|
| 105 |
-
supabaseAdmin.from("users_subscriptions").select("subscription_id,startdate,enddate,num_players,num_sessions
|
| 106 |
]);
|
| 107 |
if (error) throw new Error(`Unable to load user: ${error.message}`);
|
| 108 |
if (subError) throw new Error(`Unable to load user plan: ${subError.message}`);
|
|
@@ -135,8 +135,8 @@ export function registerAdminRoutes(app: Express) {
|
|
| 135 |
if (!plan) return fail(res, 404, "Plan not found.");
|
| 136 |
const { data: row, error } = await supabaseAdmin.from("users_subscriptions").upsert({
|
| 137 |
user_id: id.data, subscription_id: plan.subscription_id, startdate: input.data.startDate, enddate: input.data.endDate,
|
| 138 |
-
num_players: 0, num_sessions: 0,
|
| 139 |
-
}, { onConflict: "user_id" }).select("user_id,subscription_id,startdate,enddate,num_players,num_sessions
|
| 140 |
if (error) throw new Error(`Unable to assign plan: ${error.message}`);
|
| 141 |
invalidateSubscriptionCache(id.data);
|
| 142 |
return res.json({ subscription: row });
|
|
@@ -145,7 +145,7 @@ export function registerAdminRoutes(app: Express) {
|
|
| 145 |
app.delete("/api/admin/users/:userId/subscription", asyncRoute(async (req, res) => {
|
| 146 |
if (!await admin(req, res)) return;
|
| 147 |
const id = uuid.safeParse(req.params.userId); if (!id.success) return res.sendStatus(404);
|
| 148 |
-
const { error } = await supabaseAdmin.from("users_subscriptions").update({ subscription_id: null, num_players: 0, num_sessions: 0
|
| 149 |
.eq("user_id", id.data);
|
| 150 |
if (error) throw new Error(`Unable to cancel plan: ${error.message}`);
|
| 151 |
invalidateSubscriptionCache(id.data);
|
|
|
|
| 64 |
if (error) throw new Error(`Unable to load user directory: ${error.message}`);
|
| 65 |
const ids = (data ?? []).map((row) => row.user_id);
|
| 66 |
const [subs, counts] = ids.length ? await Promise.all([
|
| 67 |
+
supabaseAdmin.from("users_subscriptions").select("user_id,subscription_id,startdate,enddate,num_players,num_sessions").in("user_id", ids),
|
| 68 |
supabaseAdmin.from("sessions").select("user_id").in("user_id", ids),
|
| 69 |
]) : [{ data: [], error: null }, { data: [], error: null }];
|
| 70 |
if (subs.error) throw new Error(`Unable to load user plans: ${subs.error.message}`);
|
|
|
|
| 85 |
id: row.user_id, fname: row.fname, lname: row.lname, name: `${row.fname} ${row.lname}`.trim(),
|
| 86 |
email: row.email, academy: row.academy, role: row.role, imagePath: row.image_path, accountRole: row.account_role,
|
| 87 |
subscription: plan ? { id: plan.subscription_id, name: plan.plan, startDate: assignment?.startdate, endDate: assignment?.enddate, playerLimit: plan.player_limit, sessionLimit: plan.monthly_session_limit } : null,
|
| 88 |
+
usage: { players: assignment?.num_players ?? 0, sessions: assignment?.num_sessions ?? 0, totalSessions: sessionCounts.get(row.user_id) ?? 0 },
|
| 89 |
};
|
| 90 |
}) });
|
| 91 |
}));
|
|
|
|
| 102 |
const id = uuid.safeParse(req.params.userId); if (!id.success) return res.sendStatus(404);
|
| 103 |
const [{ data: user, error }, { data: assignment, error: subError }] = await Promise.all([
|
| 104 |
supabaseAdmin.from("users").select("user_id,fname,lname,email,academy,role,image_path,account_role").eq("user_id", id.data).maybeSingle(),
|
| 105 |
+
supabaseAdmin.from("users_subscriptions").select("subscription_id,startdate,enddate,num_players,num_sessions").eq("user_id", id.data).maybeSingle(),
|
| 106 |
]);
|
| 107 |
if (error) throw new Error(`Unable to load user: ${error.message}`);
|
| 108 |
if (subError) throw new Error(`Unable to load user plan: ${subError.message}`);
|
|
|
|
| 135 |
if (!plan) return fail(res, 404, "Plan not found.");
|
| 136 |
const { data: row, error } = await supabaseAdmin.from("users_subscriptions").upsert({
|
| 137 |
user_id: id.data, subscription_id: plan.subscription_id, startdate: input.data.startDate, enddate: input.data.endDate,
|
| 138 |
+
num_players: 0, num_sessions: 0,
|
| 139 |
+
}, { onConflict: "user_id" }).select("user_id,subscription_id,startdate,enddate,num_players,num_sessions").single();
|
| 140 |
if (error) throw new Error(`Unable to assign plan: ${error.message}`);
|
| 141 |
invalidateSubscriptionCache(id.data);
|
| 142 |
return res.json({ subscription: row });
|
|
|
|
| 145 |
app.delete("/api/admin/users/:userId/subscription", asyncRoute(async (req, res) => {
|
| 146 |
if (!await admin(req, res)) return;
|
| 147 |
const id = uuid.safeParse(req.params.userId); if (!id.success) return res.sendStatus(404);
|
| 148 |
+
const { error } = await supabaseAdmin.from("users_subscriptions").update({ subscription_id: null, num_players: 0, num_sessions: 0 })
|
| 149 |
.eq("user_id", id.data);
|
| 150 |
if (error) throw new Error(`Unable to cancel plan: ${error.message}`);
|
| 151 |
invalidateSubscriptionCache(id.data);
|
src/analysis.ts
CHANGED
|
@@ -54,9 +54,9 @@ export function analysisRouter(db: SupabaseClient, secret: string) {
|
|
| 54 |
}
|
| 55 |
if (!isAdmin) {
|
| 56 |
const { data: usage, error: usageError } = await db.from("users_subscriptions")
|
| 57 |
-
.select("
|
| 58 |
if (usageError) return res.status(503).json({ error: "Unable to check your session quota." });
|
| 59 |
-
const usedSessions = Number(usage?.
|
| 60 |
const sessionLimit = subscription?.monthly_session_limit == null ? null : Number(subscription.monthly_session_limit);
|
| 61 |
if (sessionLimit !== null && usedSessions >= sessionLimit) {
|
| 62 |
return res.status(403).json({
|
|
|
|
| 54 |
}
|
| 55 |
if (!isAdmin) {
|
| 56 |
const { data: usage, error: usageError } = await db.from("users_subscriptions")
|
| 57 |
+
.select("num_sessions").eq("user_id", userId).maybeSingle();
|
| 58 |
if (usageError) return res.status(503).json({ error: "Unable to check your session quota." });
|
| 59 |
+
const usedSessions = Number(usage?.num_sessions ?? 0);
|
| 60 |
const sessionLimit = subscription?.monthly_session_limit == null ? null : Number(subscription.monthly_session_limit);
|
| 61 |
if (sessionLimit !== null && usedSessions >= sessionLimit) {
|
| 62 |
return res.status(403).json({
|
src/server.ts
CHANGED
|
@@ -201,10 +201,10 @@ app.get("/api/plan-billing", async (req, res, next) => {
|
|
| 201 |
const userId = current.user.id;
|
| 202 |
if (await userIsAdmin(userId)) {
|
| 203 |
res.set("Cache-Control", "no-store");
|
| 204 |
-
return res.json({ subscription: null, usage: { players: 0, sessions: 0
|
| 205 |
}
|
| 206 |
const assignmentResult = await supabaseAdmin.from("users_subscriptions")
|
| 207 |
-
.select("subscription_id,startdate,enddate,num_players,num_sessions
|
| 208 |
if (assignmentResult.error) throw new Error(`Unable to load plan assignment: ${assignmentResult.error.message}`);
|
| 209 |
const assignment = assignmentResult.data;
|
| 210 |
let subscription = null;
|
|
@@ -227,8 +227,7 @@ app.get("/api/plan-billing", async (req, res, next) => {
|
|
| 227 |
}
|
| 228 |
}
|
| 229 |
res.set("Cache-Control", "no-store");
|
| 230 |
-
|
| 231 |
-
return res.json({ subscription, usage: { players: assignment?.num_players ?? 0, sessions: assignment?.num_sessions ?? 0, monthlySessions: monthlySessionUsage }, billingHistoryAvailable: false });
|
| 232 |
} catch (error) { next(error); }
|
| 233 |
});
|
| 234 |
|
|
|
|
| 201 |
const userId = current.user.id;
|
| 202 |
if (await userIsAdmin(userId)) {
|
| 203 |
res.set("Cache-Control", "no-store");
|
| 204 |
+
return res.json({ subscription: null, usage: { players: 0, sessions: 0 }, billingHistoryAvailable: false, isAdmin: true });
|
| 205 |
}
|
| 206 |
const assignmentResult = await supabaseAdmin.from("users_subscriptions")
|
| 207 |
+
.select("subscription_id,startdate,enddate,num_players,num_sessions").eq("user_id", userId).maybeSingle();
|
| 208 |
if (assignmentResult.error) throw new Error(`Unable to load plan assignment: ${assignmentResult.error.message}`);
|
| 209 |
const assignment = assignmentResult.data;
|
| 210 |
let subscription = null;
|
|
|
|
| 227 |
}
|
| 228 |
}
|
| 229 |
res.set("Cache-Control", "no-store");
|
| 230 |
+
return res.json({ subscription, usage: { players: assignment?.num_players ?? 0, sessions: Number(assignment?.num_sessions ?? 0) }, billingHistoryAvailable: false });
|
|
|
|
| 231 |
} catch (error) { next(error); }
|
| 232 |
});
|
| 233 |
|