Shoraky commited on
Commit
7ceab81
·
1 Parent(s): 6f6bf2c

Make player last name optional

Browse files
Files changed (2) hide show
  1. src/admin.ts +4 -8
  2. src/server.ts +20 -10
src/admin.ts CHANGED
@@ -15,7 +15,8 @@ const profileSchema = z.object({
15
  academy: z.string().trim().max(150).nullable(), role: z.string().trim().max(50).nullable(),
16
  }).strict();
17
  const playerSchema = z.object({
18
- name: z.string().trim().min(1).max(200), age: z.number().int().min(4).max(60).nullable().optional(),
 
19
  heightCm: z.number().min(100).max(230).nullable().optional(), weightKg: z.number().min(20).max(150).nullable().optional(),
20
  club: z.string().trim().max(150).nullable().optional(), position: z.string().trim().max(50).nullable().optional(),
21
  gender: z.enum(["male", "female"]).nullable().optional(), userId: uuid.nullable().optional(),
@@ -26,10 +27,6 @@ function asyncRoute(handler: (req: Request, res: Response) => Promise<unknown>)
26
  return (req: Request, res: Response, next: NextFunction) => { void handler(req, res).catch(next); };
27
  }
28
  async function admin(req: Request, res: Response) { return requireAdmin(req, res); }
29
- function playerName(name: string) {
30
- const [fname, ...parts] = name.split(/\s+/);
31
- return { fname, lname: parts.join(" ") };
32
- }
33
  function ageDate(age?: number | null) {
34
  if (age == null) return null;
35
  const birth = new Date();
@@ -200,11 +197,10 @@ export function registerAdminRoutes(app: Express) {
200
  if (error) throw new Error(`Unable to verify player owner: ${error.message}`);
201
  if (!data) return fail(res, 404, "Player owner not found.");
202
  }
203
- const { fname, lname } = playerName(input.data.name);
204
  const { data, error } = await supabaseAdmin.rpc("admin_create_player", {
205
  p_user_id: input.data.userId ?? null,
206
- p_fname: fname,
207
- p_lname: lname,
208
  p_bdate: ageDate(input.data.age),
209
  p_height: input.data.heightCm ?? null,
210
  p_weight: input.data.weightKg ?? null,
 
15
  academy: z.string().trim().max(150).nullable(), role: z.string().trim().max(50).nullable(),
16
  }).strict();
17
  const playerSchema = z.object({
18
+ fname: z.string().trim().min(1).max(100), lname: z.string().trim().max(100).optional().default(""),
19
+ age: z.number().int().min(4).max(60).nullable().optional(),
20
  heightCm: z.number().min(100).max(230).nullable().optional(), weightKg: z.number().min(20).max(150).nullable().optional(),
21
  club: z.string().trim().max(150).nullable().optional(), position: z.string().trim().max(50).nullable().optional(),
22
  gender: z.enum(["male", "female"]).nullable().optional(), userId: uuid.nullable().optional(),
 
27
  return (req: Request, res: Response, next: NextFunction) => { void handler(req, res).catch(next); };
28
  }
29
  async function admin(req: Request, res: Response) { return requireAdmin(req, res); }
 
 
 
 
30
  function ageDate(age?: number | null) {
31
  if (age == null) return null;
32
  const birth = new Date();
 
197
  if (error) throw new Error(`Unable to verify player owner: ${error.message}`);
198
  if (!data) return fail(res, 404, "Player owner not found.");
199
  }
 
200
  const { data, error } = await supabaseAdmin.rpc("admin_create_player", {
201
  p_user_id: input.data.userId ?? null,
202
+ p_fname: input.data.fname,
203
+ p_lname: input.data.lname,
204
  p_bdate: ageDate(input.data.age),
205
  p_height: input.data.heightCm ?? null,
206
  p_weight: input.data.weightKg ?? null,
src/server.ts CHANGED
@@ -72,17 +72,24 @@ const actionReviewSchema = z.object({
72
  feedback: z.array(z.string().trim().min(1).max(2000)).max(30),
73
  }).strict();
74
  const playerProfileSchema = z.object({
75
- name: z.string().trim().min(1).max(200),
 
 
76
  age: z.string().regex(/^(|\d{1,2})$/),
77
  heightCm: z.string().regex(/^(|\d{2,3}(?:\.\d{1,2})?)$/),
78
  weightKg: z.string().regex(/^(|\d{1,3}(?:\.\d{1,2})?)$/),
79
  club: z.string().trim().max(150),
80
  position: z.string().trim().max(50),
81
  gender: z.union([z.enum(["male", "female"]), z.literal("")]),
82
- }).strict();
 
 
 
 
 
83
  const playerCreateSchema = z.object({
84
  fname: z.string().trim().min(1).max(100),
85
- lname: z.string().trim().min(1).max(100),
86
  age: z.string().regex(/^(|\d{1,2})$/),
87
  heightCm: z.string().regex(/^(|\d{2,3}(?:\.\d{1,2})?)$/),
88
  weightKg: z.string().regex(/^(|\d{1,3}(?:\.\d{1,2})?)$/),
@@ -290,6 +297,8 @@ app.get("/api/owned-players", async (req, res, next) => {
290
  const ownedPlayers = admin ? data ?? [] : (data ?? []).filter((player) => player.user_id === current.user.id);
291
  return res.json({ players: ownedPlayers.map((player) => ({
292
  playerId: player.player_id,
 
 
293
  name: [player.fname, player.lname].filter(Boolean).join(" "),
294
  isOwner: player.user_id === current.user.id,
295
  position: player.position ?? "",
@@ -321,7 +330,7 @@ app.post("/api/players", async (req, res, next) => {
321
  const { data, error } = await supabaseAdmin.from("players").insert({
322
  user_id: current.user.id,
323
  fname: input.fname,
324
- lname: input.lname,
325
  bdate: birthday,
326
  height,
327
  weight,
@@ -338,6 +347,8 @@ app.post("/api/players", async (req, res, next) => {
338
  if (error) throw new Error(`Unable to create player: ${error.message}`);
339
  return res.status(201).json({ player: {
340
  playerId: data.player_id,
 
 
341
  name: [data.fname, data.lname].filter(Boolean).join(" "),
342
  position: data.position ?? "",
343
  club: data.club ?? "",
@@ -364,9 +375,6 @@ app.put("/api/players/:playerId", async (req, res, next) => {
364
  .select("player_id,bdate").eq("player_id", id.data).eq("user_id", current.user.id).maybeSingle();
365
  if (readError) throw new Error(`Unable to load player: ${readError.message}`);
366
  if (!existing) return res.status(404).json({ error: "Player not found." });
367
- const [fname, ...lastParts] = input.name.split(/\s+/);
368
- const lname = lastParts.join(" ");
369
- if (fname.length > 100 || lname.length > 100) return res.status(400).json({ error: "Player first and last names must be 100 characters or fewer." });
370
  let birthday = existing.bdate;
371
  if (age === null) birthday = null;
372
  else if (ageFromBirthday(existing.bdate) !== String(age)) {
@@ -375,8 +383,8 @@ app.put("/api/players/:playerId", async (req, res, next) => {
375
  birthday = date.toISOString().slice(0, 10);
376
  }
377
  const { error } = await supabaseAdmin.from("players").update({
378
- fname,
379
- lname,
380
  bdate: birthday,
381
  height,
382
  weight,
@@ -491,6 +499,8 @@ app.get("/api/players-overview", async (req, res, next) => {
491
  return {
492
  playerId: row.player_id,
493
  isOwner: row.user_id === current.user.id,
 
 
494
  name: [row.fname, row.lname].filter(Boolean).join(" "),
495
  age: ageFromBirthday(row.bdate),
496
  heightCm: row.height == null ? "" : String(row.height),
@@ -573,7 +583,7 @@ app.get("/api/players-overview/:playerId", async (req, res, next) => {
573
  const rankIndex = rankedPlayers.findIndex((entry) => entry.id === playerId.data);
574
  const squadAverage = { byQuality: [], byAction: squadByAction };
575
  return res.json({
576
- playerId: player.player_id, name: [player.fname, player.lname].filter(Boolean).join(" "),
577
  isOwner: player.user_id === current.user.id,
578
  age: ageFromBirthday(player.bdate),
579
  heightCm: player.height == null ? "" : String(player.height), weightKg: player.weight == null ? "" : String(player.weight),
 
72
  feedback: z.array(z.string().trim().min(1).max(2000)).max(30),
73
  }).strict();
74
  const playerProfileSchema = z.object({
75
+ fname: z.string().trim().min(1).max(100).optional(),
76
+ lname: z.string().trim().max(100).optional(),
77
+ name: z.string().trim().max(200).optional(),
78
  age: z.string().regex(/^(|\d{1,2})$/),
79
  heightCm: z.string().regex(/^(|\d{2,3}(?:\.\d{1,2})?)$/),
80
  weightKg: z.string().regex(/^(|\d{1,3}(?:\.\d{1,2})?)$/),
81
  club: z.string().trim().max(150),
82
  position: z.string().trim().max(50),
83
  gender: z.union([z.enum(["male", "female"]), z.literal("")]),
84
+ }).strict().transform((input) => {
85
+ const legacyNameParts = (input.name ?? "").split(/\s+/).filter(Boolean);
86
+ const fname = input.fname ?? legacyNameParts.shift() ?? "";
87
+ const lname = input.lname ?? legacyNameParts.join(" ");
88
+ return { ...input, fname, lname };
89
+ }).refine((input) => Boolean(input.fname), { message: "First name is required." });
90
  const playerCreateSchema = z.object({
91
  fname: z.string().trim().min(1).max(100),
92
+ lname: z.string().trim().max(100).optional().default(""),
93
  age: z.string().regex(/^(|\d{1,2})$/),
94
  heightCm: z.string().regex(/^(|\d{2,3}(?:\.\d{1,2})?)$/),
95
  weightKg: z.string().regex(/^(|\d{1,3}(?:\.\d{1,2})?)$/),
 
297
  const ownedPlayers = admin ? data ?? [] : (data ?? []).filter((player) => player.user_id === current.user.id);
298
  return res.json({ players: ownedPlayers.map((player) => ({
299
  playerId: player.player_id,
300
+ fname: player.fname ?? "",
301
+ lname: player.lname ?? "",
302
  name: [player.fname, player.lname].filter(Boolean).join(" "),
303
  isOwner: player.user_id === current.user.id,
304
  position: player.position ?? "",
 
330
  const { data, error } = await supabaseAdmin.from("players").insert({
331
  user_id: current.user.id,
332
  fname: input.fname,
333
+ lname: input.lname || "",
334
  bdate: birthday,
335
  height,
336
  weight,
 
347
  if (error) throw new Error(`Unable to create player: ${error.message}`);
348
  return res.status(201).json({ player: {
349
  playerId: data.player_id,
350
+ fname: data.fname ?? "",
351
+ lname: data.lname ?? "",
352
  name: [data.fname, data.lname].filter(Boolean).join(" "),
353
  position: data.position ?? "",
354
  club: data.club ?? "",
 
375
  .select("player_id,bdate").eq("player_id", id.data).eq("user_id", current.user.id).maybeSingle();
376
  if (readError) throw new Error(`Unable to load player: ${readError.message}`);
377
  if (!existing) return res.status(404).json({ error: "Player not found." });
 
 
 
378
  let birthday = existing.bdate;
379
  if (age === null) birthday = null;
380
  else if (ageFromBirthday(existing.bdate) !== String(age)) {
 
383
  birthday = date.toISOString().slice(0, 10);
384
  }
385
  const { error } = await supabaseAdmin.from("players").update({
386
+ fname: input.fname,
387
+ lname: input.lname || "",
388
  bdate: birthday,
389
  height,
390
  weight,
 
499
  return {
500
  playerId: row.player_id,
501
  isOwner: row.user_id === current.user.id,
502
+ fname: row.fname ?? "",
503
+ lname: row.lname ?? "",
504
  name: [row.fname, row.lname].filter(Boolean).join(" "),
505
  age: ageFromBirthday(row.bdate),
506
  heightCm: row.height == null ? "" : String(row.height),
 
583
  const rankIndex = rankedPlayers.findIndex((entry) => entry.id === playerId.data);
584
  const squadAverage = { byQuality: [], byAction: squadByAction };
585
  return res.json({
586
+ playerId: player.player_id, fname: player.fname ?? "", lname: player.lname ?? "", name: [player.fname, player.lname].filter(Boolean).join(" "),
587
  isOwner: player.user_id === current.user.id,
588
  age: ageFromBirthday(player.bdate),
589
  heightCm: player.height == null ? "" : String(player.height), weightKg: player.weight == null ? "" : String(player.weight),