Shoraky commited on
Commit
f5dc5bf
·
1 Parent(s): 99cf4c0

Share player and session browsing

Browse files
Files changed (3) hide show
  1. src/analysis.ts +2 -2
  2. src/profile-images.ts +1 -5
  3. src/server.ts +11 -20
src/analysis.ts CHANGED
@@ -62,9 +62,9 @@ export function analysisRouter(db: SupabaseClient, secret: string) {
62
  });
63
  }
64
  const { data: player, error: playerError } = await db.from("players")
65
- .select("player_id").eq("player_id", input.playerId).maybeSingle();
66
  if (playerError) return res.status(503).json({ error: "Unable to verify the selected player." });
67
- if (!player) return res.status(404).json({ error: "Choose a player from the available roster before starting analysis." });
68
  const { data, error } = await db.rpc("begin_analysis", {
69
  p_session_id: input.clientId, p_user_id: userId,
70
  p_player_id: input.playerId,
 
62
  });
63
  }
64
  const { data: player, error: playerError } = await db.from("players")
65
+ .select("player_id").eq("player_id", input.playerId).eq("user_id", userId).maybeSingle();
66
  if (playerError) return res.status(503).json({ error: "Unable to verify the selected player." });
67
+ if (!player) return res.status(404).json({ error: "Choose a player from your own roster before starting analysis." });
68
  const { data, error } = await db.rpc("begin_analysis", {
69
  p_session_id: input.clientId, p_user_id: userId,
70
  p_player_id: input.playerId,
src/profile-images.ts CHANGED
@@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto";
2
  import express, { type Express, type Request, type Response } from "express";
3
  import { z } from "zod";
4
  import { authenticatedUser } from "./auth.js";
5
- import { activeSubscriptionFor } from "./subscriptions.js";
6
  import { supabaseAdmin } from "./supabase.js";
7
 
8
  const BUCKET = "profile-images";
@@ -121,12 +120,9 @@ export function registerProfileImageRoutes(app: Express) {
121
  if (!current) return res.status(401).json({ error: "Authentication required." });
122
  const id = playerIdSchema.safeParse(req.params.playerId);
123
  if (!id.success) return res.status(404).json({ error: "Player not found." });
124
- const { data, error } = await supabaseAdmin.from("players").select("user_id,image_path").eq("player_id", id.data).maybeSingle();
125
  if (error) throw new Error(`Unable to load player image: ${error.message}`);
126
  if (!data) return res.status(404).json({ error: "Player not found." });
127
- if (data.user_id !== current.user.id && !await activeSubscriptionFor(current.user.id, { refresh: true })) {
128
- return res.status(403).json({ error: "An active subscription is required to view shared player profiles.", code: "subscription_required" });
129
- }
130
  return await sendImage(res, safePreviousPath("players", id.data, data?.image_path));
131
  } catch (error) { next(error); }
132
  });
 
2
  import express, { type Express, type Request, type Response } from "express";
3
  import { z } from "zod";
4
  import { authenticatedUser } from "./auth.js";
 
5
  import { supabaseAdmin } from "./supabase.js";
6
 
7
  const BUCKET = "profile-images";
 
120
  if (!current) return res.status(401).json({ error: "Authentication required." });
121
  const id = playerIdSchema.safeParse(req.params.playerId);
122
  if (!id.success) return res.status(404).json({ error: "Player not found." });
123
+ const { data, error } = await supabaseAdmin.from("players").select("image_path").eq("player_id", id.data).maybeSingle();
124
  if (error) throw new Error(`Unable to load player image: ${error.message}`);
125
  if (!data) return res.status(404).json({ error: "Player not found." });
 
 
 
126
  return await sendImage(res, safePreviousPath("players", id.data, data?.image_path));
127
  } catch (error) { next(error); }
128
  });
src/server.ts CHANGED
@@ -202,13 +202,13 @@ app.get("/api/owned-players", async (req, res, next) => {
202
  try {
203
  const current = await authenticatedUser(req, res);
204
  if (!current) return res.status(401).json({ error: "Authentication required." });
205
- const subscribed = Boolean(await activeSubscriptionFor(current.user.id, { refresh: true }));
206
  const { data, error } = await supabaseAdmin.from("players")
207
  .select("player_id,user_id,fname,lname,position,club,gender,image_path")
208
  .order("fname", { ascending: true });
209
  if (error) throw new Error(`Unable to load players: ${error.message}`);
210
- const visiblePlayers = subscribed ? (data ?? []) : (data ?? []).filter((player) => player.user_id === current.user.id);
211
- return res.json({ players: visiblePlayers.map((player) => ({
 
212
  playerId: player.player_id,
213
  name: [player.fname, player.lname].filter(Boolean).join(" "),
214
  isOwner: player.user_id === current.user.id,
@@ -312,24 +312,22 @@ app.put("/api/players/:playerId", async (req, res, next) => {
312
  } catch (error) { next(error); }
313
  });
314
 
315
- // Player management and analysis choices are scoped to the signed-in owner.
 
316
  app.get("/api/players-overview", async (req, res, next) => {
317
  try {
318
  const current = await authenticatedUser(req, res);
319
  if (!current) return res.status(401).json({ error: "Authentication required." });
320
 
321
- const subscription = await activeSubscriptionFor(current.user.id, { refresh: true });
322
- let playersQuery = supabaseAdmin.from("players")
323
  .select("player_id,user_id,fname,lname,bdate,height,weight,position,club,gender,image_path")
324
  .order("fname", { ascending: true });
325
- if (!subscription) playersQuery = playersQuery.eq("user_id", current.user.id);
326
  const { data: rows, error } = await playersQuery;
327
  if (error) throw new Error(`Unable to load players: ${error.message}`);
328
 
329
  const playerIds = (rows ?? []).map((row) => row.player_id);
330
  let sessionsQuery = supabaseAdmin.from("sessions").select("session_id,player_id,name,created_at")
331
  .in("player_id", playerIds).order("created_at", { ascending: false });
332
- if (!subscription) sessionsQuery = sessionsQuery.eq("user_id", current.user.id);
333
  const { data: sessions, error: sessionsError } = playerIds.length
334
  ? await sessionsQuery
335
  : { data: [], error: null };
@@ -396,22 +394,18 @@ app.get("/api/players-overview/:playerId", async (req, res, next) => {
396
  if (!current) return res.status(401).json({ error: "Authentication required." });
397
  const playerId = z.string().uuid().safeParse(req.params.playerId);
398
  if (!playerId.success) return res.status(404).json({ error: "Player not found." });
399
- const subscription = await activeSubscriptionFor(current.user.id, { refresh: true });
400
- let playerQuery = supabaseAdmin.from("players").select("player_id,user_id")
401
  .eq("player_id", playerId.data);
402
- if (!subscription) playerQuery = playerQuery.eq("user_id", current.user.id);
403
  const { data, error } = await playerQuery.maybeSingle();
404
  if (error) throw new Error(`Unable to load player: ${error.message}`);
405
  if (!data) return res.status(404).json({ error: "Player not found." });
406
 
407
- let overviewQuery = supabaseAdmin.from("players")
408
  .select("player_id,user_id,fname,lname,bdate,height,weight,position,club,gender,image_path")
409
  .order("fname", { ascending: true });
410
- if (!subscription) overviewQuery = overviewQuery.eq("user_id", current.user.id);
411
  const { data: overview, error: overviewError } = await overviewQuery;
412
  if (overviewError) throw new Error(`Unable to load players: ${overviewError.message}`);
413
- let sessionsQuery = supabaseAdmin.from("sessions").select("session_id,player_id,name,created_at");
414
- if (!subscription) sessionsQuery = sessionsQuery.eq("user_id", current.user.id);
415
  const { data: sessions, error: sessionsError } = await sessionsQuery;
416
  if (sessionsError) throw new Error(`Unable to load sessions: ${sessionsError.message}`);
417
  const { data: actions, error: actionsError } = (sessions ?? []).length
@@ -447,8 +441,7 @@ app.get("/api/players-overview/:playerId", async (req, res, next) => {
447
  const values = sessionActions.map((action) => scoreMap.get(action.action_id)).filter((score): score is number => score != null && Number.isFinite(score));
448
  return { sessionId: row.session_id, createdAt: Date.parse(row.created_at) || 0, score: meanPlayerScore(values), actionCount: sessionActions.length, scoredCount: values.length };
449
  });
450
- let peersQuery = supabaseAdmin.from("players").select("player_id");
451
- if (!subscription) peersQuery = peersQuery.eq("user_id", current.user.id);
452
  const { data: peers, error: peersError } = await peersQuery;
453
  if (peersError) throw new Error(`Unable to load roster: ${peersError.message}`);
454
  const playerScores = new Map<string, number[]>();
@@ -482,11 +475,9 @@ app.get("/api/sessions-overview", async (req, res, next) => {
482
  const current = await authenticatedUser(req, res);
483
  if (!current) return res.status(401).json({ error: "Authentication required." });
484
 
485
- const subscription = await activeSubscriptionFor(current.user.id, { refresh: true });
486
- let sessionsQuery = supabaseAdmin.from("sessions")
487
  .select("session_id,player_id,user_id,name,path,created_at,status")
488
  .order("created_at", { ascending: false });
489
- if (!subscription) sessionsQuery = sessionsQuery.eq("user_id", current.user.id);
490
  const { data: sessions, error } = await sessionsQuery;
491
  if (error) throw new Error(`Unable to load sessions: ${error.message}`);
492
  const playerIds = [...new Set((sessions ?? []).flatMap((row) => row.player_id ? [row.player_id] : []))];
 
202
  try {
203
  const current = await authenticatedUser(req, res);
204
  if (!current) return res.status(401).json({ error: "Authentication required." });
 
205
  const { data, error } = await supabaseAdmin.from("players")
206
  .select("player_id,user_id,fname,lname,position,club,gender,image_path")
207
  .order("fname", { ascending: true });
208
  if (error) throw new Error(`Unable to load players: ${error.message}`);
209
+ // Analysis player choices stay scoped to the signed-in user's own roster.
210
+ const ownedPlayers = (data ?? []).filter((player) => player.user_id === current.user.id);
211
+ return res.json({ players: ownedPlayers.map((player) => ({
212
  playerId: player.player_id,
213
  name: [player.fname, player.lname].filter(Boolean).join(" "),
214
  isOwner: player.user_id === current.user.id,
 
312
  } catch (error) { next(error); }
313
  });
314
 
315
+ // Roster overview is shared for viewing; player writes and analysis choices
316
+ // remain limited to the signed-in player's own roster.
317
  app.get("/api/players-overview", async (req, res, next) => {
318
  try {
319
  const current = await authenticatedUser(req, res);
320
  if (!current) return res.status(401).json({ error: "Authentication required." });
321
 
322
+ const playersQuery = supabaseAdmin.from("players")
 
323
  .select("player_id,user_id,fname,lname,bdate,height,weight,position,club,gender,image_path")
324
  .order("fname", { ascending: true });
 
325
  const { data: rows, error } = await playersQuery;
326
  if (error) throw new Error(`Unable to load players: ${error.message}`);
327
 
328
  const playerIds = (rows ?? []).map((row) => row.player_id);
329
  let sessionsQuery = supabaseAdmin.from("sessions").select("session_id,player_id,name,created_at")
330
  .in("player_id", playerIds).order("created_at", { ascending: false });
 
331
  const { data: sessions, error: sessionsError } = playerIds.length
332
  ? await sessionsQuery
333
  : { data: [], error: null };
 
394
  if (!current) return res.status(401).json({ error: "Authentication required." });
395
  const playerId = z.string().uuid().safeParse(req.params.playerId);
396
  if (!playerId.success) return res.status(404).json({ error: "Player not found." });
397
+ const playerQuery = supabaseAdmin.from("players").select("player_id,user_id")
 
398
  .eq("player_id", playerId.data);
 
399
  const { data, error } = await playerQuery.maybeSingle();
400
  if (error) throw new Error(`Unable to load player: ${error.message}`);
401
  if (!data) return res.status(404).json({ error: "Player not found." });
402
 
403
+ const overviewQuery = supabaseAdmin.from("players")
404
  .select("player_id,user_id,fname,lname,bdate,height,weight,position,club,gender,image_path")
405
  .order("fname", { ascending: true });
 
406
  const { data: overview, error: overviewError } = await overviewQuery;
407
  if (overviewError) throw new Error(`Unable to load players: ${overviewError.message}`);
408
+ const sessionsQuery = supabaseAdmin.from("sessions").select("session_id,player_id,name,created_at,user_id");
 
409
  const { data: sessions, error: sessionsError } = await sessionsQuery;
410
  if (sessionsError) throw new Error(`Unable to load sessions: ${sessionsError.message}`);
411
  const { data: actions, error: actionsError } = (sessions ?? []).length
 
441
  const values = sessionActions.map((action) => scoreMap.get(action.action_id)).filter((score): score is number => score != null && Number.isFinite(score));
442
  return { sessionId: row.session_id, createdAt: Date.parse(row.created_at) || 0, score: meanPlayerScore(values), actionCount: sessionActions.length, scoredCount: values.length };
443
  });
444
+ const peersQuery = supabaseAdmin.from("players").select("player_id");
 
445
  const { data: peers, error: peersError } = await peersQuery;
446
  if (peersError) throw new Error(`Unable to load roster: ${peersError.message}`);
447
  const playerScores = new Map<string, number[]>();
 
475
  const current = await authenticatedUser(req, res);
476
  if (!current) return res.status(401).json({ error: "Authentication required." });
477
 
478
+ const sessionsQuery = supabaseAdmin.from("sessions")
 
479
  .select("session_id,player_id,user_id,name,path,created_at,status")
480
  .order("created_at", { ascending: false });
 
481
  const { data: sessions, error } = await sessionsQuery;
482
  if (error) throw new Error(`Unable to load sessions: ${error.message}`);
483
  const playerIds = [...new Set((sessions ?? []).flatMap((row) => row.player_id ? [row.player_id] : []))];