58 Commits

Author SHA1 Message Date
yohlo
732afaf623 changes to twilio 2025-09-29 12:51:33 -05:00
yohlo
48aeaabeea improvements 2025-09-29 11:43:48 -05:00
yohlo
a4b9fe9065 updated bracket 2025-09-29 10:50:18 -05:00
yohlo
31e50af593 team logo compression, play around with style 2025-09-29 10:20:54 -05:00
yohlo
39053cadaa avatr contain 2025-09-26 12:55:04 -05:00
yohlo
ea6656aa33 avatar modal 2025-09-25 21:24:57 -05:00
yohlo
92c4987372 bun 2025-09-25 16:42:55 -05:00
yohlo
b3ebf46afa new typeahead 2025-09-25 16:11:54 -05:00
yohlo
c0ef535001 bug fixes 2025-09-25 15:49:09 -05:00
yohlo
81329e4354 fix refresh issue 2025-09-24 12:20:36 -05:00
yohlo
36f3bb77d4 updates 2025-09-24 11:02:56 -05:00
yohlo
6760ea46f9 update query integration 2025-09-24 08:04:09 -05:00
yohlo
e4164cbc71 attempted upgrade 2025-09-24 00:13:41 -05:00
yohlo
94ea44c66e drawer fixes 2025-09-23 15:04:29 -05:00
yohlo
7441d1ac58 skeletons, tournament stats, polish, bug fixes 2025-09-23 14:48:04 -05:00
yohlo
7ff26229d9 dark mode default, basic tournament stats/podium 2025-09-22 19:33:58 -05:00
yohlo
b93ce38d48 play walkout songs 2025-09-22 17:57:29 -05:00
yohlo
ae934e77f4 manage team data 2025-09-22 17:24:45 -05:00
yohlo
cae5fa1c71 skeletons 2025-09-22 16:45:41 -05:00
yohlo
fc3f626313 minor cleanup 2025-09-21 11:38:10 -05:00
yohlo
1027b49258 free agents 2025-09-20 20:50:44 -05:00
yohlo
5e20b94a1f reactions SSE! 2025-09-19 20:53:05 -05:00
yohlo
f99d6efaf9 reactions, match sse, etc 2025-09-19 14:08:36 -05:00
yohlo
602e6e3473 rules, bracket page 2025-09-18 18:17:56 -05:00
yohlo
285a33c488 enroll team polish? 2025-09-17 20:20:52 -05:00
yohlo
cac42c9b29 reactions 2025-09-17 10:30:10 -05:00
yohlo
498010e3e2 various improvements 2025-09-17 09:02:20 -05:00
yohlo
c170e1e1fe working team update/create (still need enroll) 2025-09-16 13:24:39 -05:00
yohlo
cde74a04d5 work on team enrollment 2025-09-16 09:24:21 -05:00
yohlo
9a105b30c6 stats reorg, upcoming refinement 2025-09-14 23:10:05 -05:00
yohlo
8efc0a7a4b match list 2025-09-14 21:59:15 -05:00
yohlo
d11e50d4ef profile and stats improvements 2025-09-13 23:05:35 -05:00
yohlo
4bc25fb0bc stats table 2025-09-13 15:21:23 -05:00
yohlo
3be2284da9 wip tournament list 2025-09-13 14:59:02 -05:00
yohlo
a35c688a64 wip upcoming tournament page 2025-09-13 14:49:39 -05:00
yohlo
617a94262b redesign stats 2025-09-13 11:36:30 -05:00
yohlo
3fe92be980 player stats in profile 2025-09-13 11:21:05 -05:00
yohlo
7d3c0a3fa4 refresh test 2025-09-13 00:50:41 -05:00
yohlo
a926dcde07 better bracket, player stats in pb 2025-09-12 17:14:33 -05:00
yohlo
e20582897f more spotify, tts 2025-09-12 14:27:59 -05:00
yohlo
cf09014d50 spotify state resume/capture 2025-09-12 11:34:21 -05:00
yohlo
0169468114 spotify controls 2025-09-12 11:08:21 -05:00
yohlo
9d92a8a510 working runner w/o spotify 2025-09-12 10:17:51 -05:00
yohlo
51e3d5141c reset match logic 2025-09-11 16:13:45 -05:00
yohlo
97c8c9e72b i think working bracket runner 2025-09-11 16:04:46 -05:00
yohlo
3ffa6b03c7 i think working bracket runner 2025-09-11 15:59:27 -05:00
yohlo
8dfff139e1 match status 2025-09-11 14:04:05 -05:00
yohlo
22be6682dd some changes 2025-09-11 13:35:33 -05:00
yohlo
c74da09bde upcoming tournament page, minor changes 2025-09-09 23:20:19 -05:00
yohlo
c5d69f1a19 seed tournament done 2025-09-07 11:55:41 -05:00
yohlo
2396464a19 better brackets, info types 2025-09-07 00:52:28 -05:00
yohlo
cb83ea06fa preview bracket 2025-09-06 18:38:05 -05:00
d2e6849bca more bracket work 2025-09-04 11:37:33 -05:00
yohlo
2f6950ee9e restarting brackets 2025-09-03 21:57:47 -05:00
1a21171ae8 updated bracket type 2025-09-03 15:48:34 -05:00
0439e4c8b9 fix login bug 2025-09-03 14:30:19 -05:00
07388e30da refresh progress 2025-08-31 10:23:18 -05:00
b7d14be590 add exclude option to navlink active indicator 2025-08-31 10:02:17 -05:00
233 changed files with 23606 additions and 1843 deletions

View File

@@ -1,6 +0,0 @@
-- Note: It's highly recommended to use more secure passwords
-- create db/user for application data/drizzle orm
CREATE USER app_svc WITH PASSWORD 'password';
CREATE DATABASE app_db;
GRANT ALL PRIVILEGES ON DATABASE app_db TO app_svc;

3
.gitignore vendored
View File

@@ -19,4 +19,5 @@ yarn.lock
/playwright/.cache/
/scripts/
/pb_data/
/.tanstack/
/.tanstack/
/dist/

16
.nitro/types/nitro-config.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
// Generated by nitro
// App Config
import type { Defu } from 'defu'
type UserAppConfig = Defu<{}, []>
declare module "nitropack/types" {
interface AppConfig extends UserAppConfig {}
interface NitroRuntimeConfig {
}
}
export {}

1
.nitro/types/nitro-imports.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {}

8
.nitro/types/nitro-routes.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
// Generated by nitro
import type { Serialize, Simplify } from "nitropack/types";
declare module "nitropack/types" {
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T
interface InternalApi {
}
}
export {}

3
.nitro/types/nitro.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference path="./nitro-routes.d.ts" />
/// <reference path="./nitro-config.d.ts" />
/// <reference path="./nitro-imports.d.ts" />

317
brackets/10.json Normal file
View File

@@ -0,0 +1,317 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
}
],
[
{
"lid": 8,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 5,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 6,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 18,
"round": 2,
"order": 11,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 2,
"order": 12,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 23,
"round": 3,
"order": 16,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 25,
"round": 4,
"order": 18,
"bye": false,
"home_from_lid": 23,
"home_from_loser": false,
"away_from_lid": 24,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 5,
"order": 19,
"bye": false,
"home_from_lid": 25,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 20,
"round": 2,
"order": 13,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 16,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 2,
"order": 14,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 22,
"round": 3,
"order": 15,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 4,
"order": 17,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 22,
"away_from_loser": false,
"reset": false
}
]
]
}

323
brackets/11.json Normal file
View File

@@ -0,0 +1,323 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 7,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 5,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 7,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 18,
"round": 2,
"order": 13,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 2,
"order": 14,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 23,
"round": 3,
"order": 16,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 25,
"round": 4,
"order": 18,
"bye": false,
"home_from_lid": 23,
"home_from_loser": false,
"away_from_lid": 24,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 5,
"order": 19,
"bye": false,
"home_from_lid": 25,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": 10,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 15,
"round": 0,
"order": 9,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": 14,
"home_from_loser": false,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 20,
"round": 2,
"order": 13,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 16,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 2,
"order": 14,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 22,
"round": 3,
"order": 15,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 4,
"order": 17,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 22,
"away_from_loser": false,
"reset": false
}
]
]
}

375
brackets/12.json Normal file
View File

@@ -0,0 +1,375 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 3,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 7,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 28,
"round": 2,
"order": 17,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 29,
"round": 2,
"order": 18,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 34,
"round": 3,
"order": 23,
"bye": false,
"home_from_lid": 28,
"home_from_loser": false,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 37,
"round": 4,
"order": 26,
"bye": false,
"home_from_lid": 34,
"home_from_loser": false,
"away_from_lid": 36,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 38,
"round": 5,
"order": 27,
"bye": false,
"home_from_lid": 37,
"home_from_loser": false,
"away_from_lid": 37,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 20,
"round": 0,
"order": 9,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 21,
"round": 0,
"order": 10,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 22,
"round": 0,
"order": 11,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 23,
"round": 0,
"order": 12,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 24,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 25,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 26,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 27,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 30,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 24,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
},
{
"lid": 31,
"round": 2,
"order": 20,
"bye": false,
"home_from_lid": 26,
"home_from_loser": false,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 32,
"round": 3,
"order": 21,
"bye": false,
"home_from_lid": 28,
"home_from_loser": true,
"away_from_lid": 30,
"away_from_loser": false,
"reset": false
},
{
"lid": 33,
"round": 3,
"order": 22,
"bye": false,
"home_from_lid": 29,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 35,
"round": 4,
"order": 24,
"bye": false,
"home_from_lid": 32,
"home_from_loser": false,
"away_from_lid": 33,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 36,
"round": 5,
"order": 25,
"bye": false,
"home_from_lid": 34,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
}
]
]
}

401
brackets/13.json Normal file
View File

@@ -0,0 +1,401 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 7,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 2,
"order": 16,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 25,
"round": 2,
"order": 17,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 30,
"round": 3,
"order": 22,
"bye": false,
"home_from_lid": 24,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 33,
"round": 4,
"order": 25,
"bye": false,
"home_from_lid": 30,
"home_from_loser": false,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 34,
"round": 5,
"order": 26,
"bye": false,
"home_from_lid": 33,
"home_from_loser": false,
"away_from_lid": 33,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 11,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": 11,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 16,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 17,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 5,
"home_from_loser": true
},
{
"lid": 18,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
},
{
"lid": 19,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 7,
"home_from_loser": true
}
],
[
{
"lid": 20,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 26,
"round": 2,
"order": 18,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 27,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 28,
"round": 3,
"order": 20,
"bye": false,
"home_from_lid": 24,
"home_from_loser": true,
"away_from_lid": 26,
"away_from_loser": false,
"reset": false
},
{
"lid": 29,
"round": 3,
"order": 21,
"bye": false,
"home_from_lid": 25,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 31,
"round": 4,
"order": 23,
"bye": false,
"home_from_lid": 28,
"home_from_loser": false,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 32,
"round": 5,
"order": 24,
"bye": false,
"home_from_lid": 30,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
]
]
}

407
brackets/14.json Normal file
View File

@@ -0,0 +1,407 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 14,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 7,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": 6,
"home_from_loser": false,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 25,
"round": 2,
"order": 20,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 30,
"round": 3,
"order": 25,
"bye": false,
"home_from_lid": 24,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 33,
"round": 4,
"order": 28,
"bye": false,
"home_from_lid": 30,
"home_from_loser": false,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 34,
"round": 5,
"order": 29,
"bye": false,
"home_from_lid": 33,
"home_from_loser": false,
"away_from_lid": 33,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 11,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": 13,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 16,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 17,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 5,
"home_from_loser": true
},
{
"lid": 18,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
},
{
"lid": 19,
"round": 0,
"order": 14,
"bye": false,
"home_from_lid": 6,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 20,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 2,
"order": 21,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 27,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 28,
"round": 3,
"order": 23,
"bye": false,
"home_from_lid": 24,
"home_from_loser": true,
"away_from_lid": 26,
"away_from_loser": false,
"reset": false
},
{
"lid": 29,
"round": 3,
"order": 24,
"bye": false,
"home_from_lid": 25,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 31,
"round": 4,
"order": 26,
"bye": false,
"home_from_lid": 28,
"home_from_loser": false,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 32,
"round": 5,
"order": 27,
"bye": false,
"home_from_lid": 30,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
]
]
}

413
brackets/15.json Normal file
View File

@@ -0,0 +1,413 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_seed": 15,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 5,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 14,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 7,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 4,
"home_from_loser": false,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 6,
"home_from_loser": false,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 25,
"round": 2,
"order": 23,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 30,
"round": 3,
"order": 28,
"bye": false,
"home_from_lid": 24,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 33,
"round": 4,
"order": 31,
"bye": false,
"home_from_lid": 30,
"home_from_loser": false,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 34,
"round": 5,
"order": 32,
"bye": false,
"home_from_lid": 33,
"home_from_loser": false,
"away_from_lid": 33,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 11,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": 15,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 16,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 17,
"round": 0,
"order": 16,
"bye": false,
"home_from_lid": 4,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 18,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
},
{
"lid": 19,
"round": 0,
"order": 17,
"bye": false,
"home_from_lid": 6,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 20,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 19,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 20,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 21,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 2,
"order": 24,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 27,
"round": 2,
"order": 25,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 28,
"round": 3,
"order": 26,
"bye": false,
"home_from_lid": 24,
"home_from_loser": true,
"away_from_lid": 26,
"away_from_loser": false,
"reset": false
},
{
"lid": 29,
"round": 3,
"order": 27,
"bye": false,
"home_from_lid": 25,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 31,
"round": 4,
"order": 29,
"bye": false,
"home_from_lid": 28,
"home_from_loser": false,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 32,
"round": 5,
"order": 30,
"bye": false,
"home_from_lid": 30,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
]
]
}

419
brackets/16.json Normal file
View File

@@ -0,0 +1,419 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_seed": 16,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 1,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_seed": 15,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 5,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 14,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 7,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 8,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 0,
"home_from_loser": false,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 4,
"home_from_loser": false,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 6,
"home_from_loser": false,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 2,
"order": 25,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 25,
"round": 2,
"order": 26,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 30,
"round": 3,
"order": 31,
"bye": false,
"home_from_lid": 24,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 33,
"round": 4,
"order": 34,
"bye": false,
"home_from_lid": 30,
"home_from_loser": false,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 34,
"round": 5,
"order": 35,
"bye": false,
"home_from_lid": 33,
"home_from_loser": false,
"away_from_lid": 33,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 11,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": 17,
"bye": false,
"home_from_lid": 0,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": 18,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 16,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 17,
"round": 0,
"order": 19,
"bye": false,
"home_from_lid": 4,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 18,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
},
{
"lid": 19,
"round": 0,
"order": 20,
"bye": false,
"home_from_lid": 6,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 20,
"round": 1,
"order": 21,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 22,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 23,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 24,
"bye": false,
"home_from_lid": 8,
"home_from_loser": true,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 2,
"order": 27,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 27,
"round": 2,
"order": 28,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 28,
"round": 3,
"order": 29,
"bye": false,
"home_from_lid": 24,
"home_from_loser": true,
"away_from_lid": 26,
"away_from_loser": false,
"reset": false
},
{
"lid": 29,
"round": 3,
"order": 30,
"bye": false,
"home_from_lid": 25,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 31,
"round": 4,
"order": 32,
"bye": false,
"home_from_lid": 28,
"home_from_loser": false,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 32,
"round": 5,
"order": 33,
"bye": false,
"home_from_lid": 30,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
]
]
}

605
brackets/17.json Normal file
View File

@@ -0,0 +1,605 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_seed": 15,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 14,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 36,
"round": 2,
"order": 15,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 37,
"round": 2,
"order": 16,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 38,
"round": 2,
"order": 17,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 39,
"round": 2,
"order": 18,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 46,
"round": 3,
"order": 25,
"bye": false,
"home_from_lid": 36,
"home_from_loser": false,
"away_from_lid": 37,
"away_from_loser": false,
"reset": false
},
{
"lid": 47,
"round": 3,
"order": 26,
"bye": false,
"home_from_lid": 38,
"home_from_loser": false,
"away_from_lid": 39,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 51,
"round": 4,
"order": 30,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 53,
"round": 5,
"order": 32,
"bye": false,
"home_from_lid": 51,
"home_from_loser": false,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 54,
"round": 6,
"order": 33,
"bye": false,
"home_from_lid": 53,
"home_from_loser": false,
"away_from_lid": 53,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": 10,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 19,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 17,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
}
],
[
{
"lid": 32,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
},
{
"lid": 33,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 20,
"away_from_loser": true,
"reset": false
},
{
"lid": 34,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 18,
"away_from_loser": true,
"reset": false
},
{
"lid": 35,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 16,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 40,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 37,
"home_from_loser": true,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
},
{
"lid": 41,
"round": 2,
"order": 20,
"bye": false,
"home_from_lid": 36,
"home_from_loser": true,
"away_from_lid": 33,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 2,
"order": 21,
"bye": false,
"home_from_lid": 39,
"home_from_loser": true,
"away_from_lid": 34,
"away_from_loser": false,
"reset": false
},
{
"lid": 43,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 38,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 44,
"round": 3,
"order": 23,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 45,
"round": 3,
"order": 24,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 4,
"order": 27,
"bye": false,
"home_from_lid": 46,
"home_from_loser": true,
"away_from_lid": 44,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 4,
"order": 28,
"bye": false,
"home_from_lid": 47,
"home_from_loser": true,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 50,
"round": 5,
"order": 29,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 52,
"round": 6,
"order": 31,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 50,
"away_from_loser": false,
"reset": false
}
]
]
}

611
brackets/18.json Normal file
View File

@@ -0,0 +1,611 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 14,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 36,
"round": 2,
"order": 17,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 37,
"round": 2,
"order": 18,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 38,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 39,
"round": 2,
"order": 20,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 46,
"round": 3,
"order": 27,
"bye": false,
"home_from_lid": 36,
"home_from_loser": false,
"away_from_lid": 37,
"away_from_loser": false,
"reset": false
},
{
"lid": 47,
"round": 3,
"order": 28,
"bye": false,
"home_from_lid": 38,
"home_from_loser": false,
"away_from_lid": 39,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 51,
"round": 4,
"order": 32,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 53,
"round": 5,
"order": 34,
"bye": false,
"home_from_lid": 51,
"home_from_loser": false,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 54,
"round": 6,
"order": 35,
"bye": false,
"home_from_lid": 53,
"home_from_loser": false,
"away_from_lid": 53,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": 11,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": 12,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 17,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
}
],
[
{
"lid": 32,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
},
{
"lid": 33,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 20,
"away_from_loser": true,
"reset": false
},
{
"lid": 34,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
},
{
"lid": 35,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 16,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 40,
"round": 2,
"order": 21,
"bye": false,
"home_from_lid": 37,
"home_from_loser": true,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
},
{
"lid": 41,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 36,
"home_from_loser": true,
"away_from_lid": 33,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 2,
"order": 23,
"bye": false,
"home_from_lid": 39,
"home_from_loser": true,
"away_from_lid": 34,
"away_from_loser": false,
"reset": false
},
{
"lid": 43,
"round": 2,
"order": 24,
"bye": false,
"home_from_lid": 38,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 44,
"round": 3,
"order": 25,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 45,
"round": 3,
"order": 26,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 4,
"order": 29,
"bye": false,
"home_from_lid": 46,
"home_from_loser": true,
"away_from_lid": 44,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 4,
"order": 30,
"bye": false,
"home_from_lid": 47,
"home_from_loser": true,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 50,
"round": 5,
"order": 31,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 52,
"round": 6,
"order": 33,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 50,
"away_from_loser": false,
"reset": false
}
]
]
}

617
brackets/19.json Normal file
View File

@@ -0,0 +1,617 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 13,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 36,
"round": 2,
"order": 19,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 37,
"round": 2,
"order": 20,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 38,
"round": 2,
"order": 21,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 39,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 46,
"round": 3,
"order": 29,
"bye": false,
"home_from_lid": 36,
"home_from_loser": false,
"away_from_lid": 37,
"away_from_loser": false,
"reset": false
},
{
"lid": 47,
"round": 3,
"order": 30,
"bye": false,
"home_from_lid": 38,
"home_from_loser": false,
"away_from_lid": 39,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 51,
"round": 4,
"order": 34,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 53,
"round": 5,
"order": 36,
"bye": false,
"home_from_lid": 51,
"home_from_loser": false,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 54,
"round": 6,
"order": 37,
"bye": false,
"home_from_lid": 53,
"home_from_loser": false,
"away_from_lid": 53,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": 12,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": 13,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": 14,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 32,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
},
{
"lid": 33,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 20,
"away_from_loser": true,
"reset": false
},
{
"lid": 34,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
},
{
"lid": 35,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 40,
"round": 2,
"order": 23,
"bye": false,
"home_from_lid": 37,
"home_from_loser": true,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
},
{
"lid": 41,
"round": 2,
"order": 24,
"bye": false,
"home_from_lid": 36,
"home_from_loser": true,
"away_from_lid": 33,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 2,
"order": 25,
"bye": false,
"home_from_lid": 39,
"home_from_loser": true,
"away_from_lid": 34,
"away_from_loser": false,
"reset": false
},
{
"lid": 43,
"round": 2,
"order": 26,
"bye": false,
"home_from_lid": 38,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 44,
"round": 3,
"order": 27,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 45,
"round": 3,
"order": 28,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 4,
"order": 31,
"bye": false,
"home_from_lid": 46,
"home_from_loser": true,
"away_from_lid": 44,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 4,
"order": 32,
"bye": false,
"home_from_lid": 47,
"home_from_loser": true,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 50,
"round": 5,
"order": 33,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 52,
"round": 6,
"order": 35,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 50,
"away_from_loser": false,
"reset": false
}
]
]
}

623
brackets/20.json Normal file
View File

@@ -0,0 +1,623 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13,
"away_seed": 20,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_seed": 12,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 10,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 11,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 36,
"round": 2,
"order": 21,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 37,
"round": 2,
"order": 22,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 38,
"round": 2,
"order": 23,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 39,
"round": 2,
"order": 24,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 46,
"round": 3,
"order": 31,
"bye": false,
"home_from_lid": 36,
"home_from_loser": false,
"away_from_lid": 37,
"away_from_loser": false,
"reset": false
},
{
"lid": 47,
"round": 3,
"order": 32,
"bye": false,
"home_from_lid": 38,
"home_from_loser": false,
"away_from_lid": 39,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 51,
"round": 4,
"order": 36,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 53,
"round": 5,
"order": 38,
"bye": false,
"home_from_lid": 51,
"home_from_loser": false,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 54,
"round": 6,
"order": 39,
"bye": false,
"home_from_lid": 53,
"home_from_loser": false,
"away_from_lid": 53,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": 13,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": 14,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": 15,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": 16,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 32,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 25,
"away_from_loser": false,
"reset": false
},
{
"lid": 33,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
},
{
"lid": 34,
"round": 1,
"order": 19,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 29,
"away_from_loser": false,
"reset": false
},
{
"lid": 35,
"round": 1,
"order": 20,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 31,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 40,
"round": 2,
"order": 25,
"bye": false,
"home_from_lid": 37,
"home_from_loser": true,
"away_from_lid": 32,
"away_from_loser": false,
"reset": false
},
{
"lid": 41,
"round": 2,
"order": 26,
"bye": false,
"home_from_lid": 36,
"home_from_loser": true,
"away_from_lid": 33,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 2,
"order": 27,
"bye": false,
"home_from_lid": 39,
"home_from_loser": true,
"away_from_lid": 34,
"away_from_loser": false,
"reset": false
},
{
"lid": 43,
"round": 2,
"order": 28,
"bye": false,
"home_from_lid": 38,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 44,
"round": 3,
"order": 29,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 45,
"round": 3,
"order": 30,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 4,
"order": 33,
"bye": false,
"home_from_lid": 46,
"home_from_loser": true,
"away_from_lid": 44,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 4,
"order": 34,
"bye": false,
"home_from_lid": 47,
"home_from_loser": true,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 50,
"round": 5,
"order": 35,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 52,
"round": 6,
"order": 37,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 50,
"away_from_loser": false,
"reset": false
}
]
]
}

737
brackets/24.json Normal file
View File

@@ -0,0 +1,737 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8
},
{
"lid": 3,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9,
"away_seed": 24,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13,
"away_seed": 20,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12,
"away_seed": 21,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10,
"away_seed": 23,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11,
"away_seed": 22,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 56,
"round": 2,
"order": 33,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 57,
"round": 2,
"order": 34,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 58,
"round": 2,
"order": 35,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 59,
"round": 2,
"order": 36,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 68,
"round": 3,
"order": 45,
"bye": false,
"home_from_lid": 56,
"home_from_loser": false,
"away_from_lid": 57,
"away_from_loser": false,
"reset": false
},
{
"lid": 69,
"round": 3,
"order": 46,
"bye": false,
"home_from_lid": 58,
"home_from_loser": false,
"away_from_lid": 59,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 74,
"round": 4,
"order": 51,
"bye": false,
"home_from_lid": 68,
"home_from_loser": false,
"away_from_lid": 69,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 77,
"round": 5,
"order": 54,
"bye": false,
"home_from_lid": 74,
"home_from_loser": false,
"away_from_lid": 76,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 78,
"round": 6,
"order": 55,
"bye": false,
"home_from_lid": 77,
"home_from_loser": false,
"away_from_lid": 77,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 40,
"round": 0,
"order": 17,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 41,
"round": 0,
"order": 18,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 42,
"round": 0,
"order": 19,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 43,
"round": 0,
"order": 20,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
},
{
"lid": 44,
"round": 0,
"order": 21,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 45,
"round": 0,
"order": 22,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 11,
"away_from_loser": true,
"reset": false
},
{
"lid": 46,
"round": 0,
"order": 23,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
},
{
"lid": 47,
"round": 0,
"order": 24,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 48,
"round": 1,
"order": 25,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 49,
"round": 1,
"order": 26,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 50,
"round": 1,
"order": 27,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 51,
"round": 1,
"order": 28,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
},
{
"lid": 52,
"round": 1,
"order": 29,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 53,
"round": 1,
"order": 30,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 11,
"away_from_loser": true,
"reset": false
},
{
"lid": 54,
"round": 1,
"order": 31,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
},
{
"lid": 55,
"round": 1,
"order": 32,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 60,
"round": 2,
"order": 37,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
},
{
"lid": 61,
"round": 2,
"order": 38,
"bye": false,
"home_from_lid": 50,
"home_from_loser": false,
"away_from_lid": 51,
"away_from_loser": false,
"reset": false
},
{
"lid": 62,
"round": 2,
"order": 39,
"bye": false,
"home_from_lid": 52,
"home_from_loser": false,
"away_from_lid": 53,
"away_from_loser": false,
"reset": false
},
{
"lid": 63,
"round": 2,
"order": 40,
"bye": false,
"home_from_lid": 54,
"home_from_loser": false,
"away_from_lid": 55,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 64,
"round": 3,
"order": 41,
"bye": false,
"home_from_lid": 57,
"home_from_loser": true,
"away_from_lid": 60,
"away_from_loser": false,
"reset": false
},
{
"lid": 65,
"round": 3,
"order": 42,
"bye": false,
"home_from_lid": 56,
"home_from_loser": true,
"away_from_lid": 61,
"away_from_loser": false,
"reset": false
},
{
"lid": 66,
"round": 3,
"order": 43,
"bye": false,
"home_from_lid": 59,
"home_from_loser": true,
"away_from_lid": 62,
"away_from_loser": false,
"reset": false
},
{
"lid": 67,
"round": 3,
"order": 44,
"bye": false,
"home_from_lid": 58,
"home_from_loser": true,
"away_from_lid": 63,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 70,
"round": 4,
"order": 47,
"bye": false,
"home_from_lid": 64,
"home_from_loser": false,
"away_from_lid": 65,
"away_from_loser": false,
"reset": false
},
{
"lid": 71,
"round": 4,
"order": 48,
"bye": false,
"home_from_lid": 66,
"home_from_loser": false,
"away_from_lid": 67,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 72,
"round": 5,
"order": 49,
"bye": false,
"home_from_lid": 68,
"home_from_loser": true,
"away_from_lid": 70,
"away_from_loser": false,
"reset": false
},
{
"lid": 73,
"round": 5,
"order": 50,
"bye": false,
"home_from_lid": 69,
"home_from_loser": true,
"away_from_lid": 71,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 75,
"round": 6,
"order": 52,
"bye": false,
"home_from_lid": 72,
"home_from_loser": false,
"away_from_lid": 73,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 76,
"round": 7,
"order": 53,
"bye": false,
"home_from_lid": 74,
"home_from_loser": true,
"away_from_lid": 75,
"away_from_loser": false,
"reset": false
}
]
]
}

783
brackets/25.json Normal file
View File

@@ -0,0 +1,783 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 25,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9,
"away_seed": 24,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13,
"away_seed": 20,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12,
"away_seed": 21,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 11,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10,
"away_seed": 23,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11,
"away_seed": 22,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 12,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 2,
"order": 28,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 2,
"order": 29,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 50,
"round": 2,
"order": 30,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 51,
"round": 2,
"order": 31,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 60,
"round": 3,
"order": 40,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
},
{
"lid": 61,
"round": 3,
"order": 41,
"bye": false,
"home_from_lid": 50,
"home_from_loser": false,
"away_from_lid": 51,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 66,
"round": 4,
"order": 46,
"bye": false,
"home_from_lid": 60,
"home_from_loser": false,
"away_from_lid": 61,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 69,
"round": 5,
"order": 49,
"bye": false,
"home_from_lid": 66,
"home_from_loser": false,
"away_from_lid": 68,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 70,
"round": 6,
"order": 50,
"bye": false,
"home_from_lid": 69,
"home_from_loser": false,
"away_from_lid": 69,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 23,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": 19,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 5,
"home_from_loser": true
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 7,
"home_from_loser": true
},
{
"lid": 32,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 19,
"home_from_loser": true
},
{
"lid": 33,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 34,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 35,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 11,
"home_from_loser": true
},
{
"lid": 36,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 17,
"home_from_loser": true
},
{
"lid": 37,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 13,
"home_from_loser": true
},
{
"lid": 38,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
},
{
"lid": 39,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 15,
"home_from_loser": true
}
],
[
{
"lid": 40,
"round": 1,
"order": 20,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 41,
"round": 1,
"order": 21,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 1,
"order": 22,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 43,
"round": 1,
"order": 23,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
},
{
"lid": 44,
"round": 1,
"order": 24,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 45,
"round": 1,
"order": 25,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 11,
"away_from_loser": true,
"reset": false
},
{
"lid": 46,
"round": 1,
"order": 26,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
},
{
"lid": 47,
"round": 1,
"order": 27,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 52,
"round": 2,
"order": 32,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 53,
"round": 2,
"order": 33,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
},
{
"lid": 54,
"round": 2,
"order": 34,
"bye": false,
"home_from_lid": 44,
"home_from_loser": false,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
},
{
"lid": 55,
"round": 2,
"order": 35,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 56,
"round": 3,
"order": 36,
"bye": false,
"home_from_lid": 49,
"home_from_loser": true,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
},
{
"lid": 57,
"round": 3,
"order": 37,
"bye": false,
"home_from_lid": 48,
"home_from_loser": true,
"away_from_lid": 53,
"away_from_loser": false,
"reset": false
},
{
"lid": 58,
"round": 3,
"order": 38,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 54,
"away_from_loser": false,
"reset": false
},
{
"lid": 59,
"round": 3,
"order": 39,
"bye": false,
"home_from_lid": 50,
"home_from_loser": true,
"away_from_lid": 55,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 62,
"round": 4,
"order": 42,
"bye": false,
"home_from_lid": 56,
"home_from_loser": false,
"away_from_lid": 57,
"away_from_loser": false,
"reset": false
},
{
"lid": 63,
"round": 4,
"order": 43,
"bye": false,
"home_from_lid": 58,
"home_from_loser": false,
"away_from_lid": 59,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 64,
"round": 5,
"order": 44,
"bye": false,
"home_from_lid": 60,
"home_from_loser": true,
"away_from_lid": 62,
"away_from_loser": false,
"reset": false
},
{
"lid": 65,
"round": 5,
"order": 45,
"bye": false,
"home_from_lid": 61,
"home_from_loser": true,
"away_from_lid": 63,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 67,
"round": 6,
"order": 47,
"bye": false,
"home_from_lid": 64,
"home_from_loser": false,
"away_from_lid": 65,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 68,
"round": 7,
"order": 48,
"bye": false,
"home_from_lid": 66,
"home_from_loser": true,
"away_from_lid": 67,
"away_from_loser": false,
"reset": false
}
]
]
}

789
brackets/26.json Normal file
View File

@@ -0,0 +1,789 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 25,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9,
"away_seed": 24,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13,
"away_seed": 20,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12,
"away_seed": 21,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 26,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 11,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10,
"away_seed": 23,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
},
{
"lid": 15,
"round": 0,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11,
"away_seed": 22,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 13,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 14,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 19,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 20,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 2,
"order": 31,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 2,
"order": 32,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 50,
"round": 2,
"order": 33,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 51,
"round": 2,
"order": 34,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 60,
"round": 3,
"order": 43,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
},
{
"lid": 61,
"round": 3,
"order": 44,
"bye": false,
"home_from_lid": 50,
"home_from_loser": false,
"away_from_lid": 51,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 66,
"round": 4,
"order": 49,
"bye": false,
"home_from_lid": 60,
"home_from_loser": false,
"away_from_lid": 61,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 69,
"round": 5,
"order": 52,
"bye": false,
"home_from_lid": 66,
"home_from_loser": false,
"away_from_lid": 68,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 70,
"round": 6,
"order": 53,
"bye": false,
"home_from_lid": 69,
"home_from_loser": false,
"away_from_lid": 69,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 23,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": 21,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 5,
"home_from_loser": true
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 7,
"home_from_loser": true
},
{
"lid": 32,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 19,
"home_from_loser": true
},
{
"lid": 33,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 34,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 35,
"round": 0,
"order": 22,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 11,
"away_from_loser": true,
"reset": false
},
{
"lid": 36,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 17,
"home_from_loser": true
},
{
"lid": 37,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 13,
"home_from_loser": true
},
{
"lid": 38,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
},
{
"lid": 39,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 15,
"home_from_loser": true
}
],
[
{
"lid": 40,
"round": 1,
"order": 23,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 41,
"round": 1,
"order": 24,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 1,
"order": 25,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 43,
"round": 1,
"order": 26,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
},
{
"lid": 44,
"round": 1,
"order": 27,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 45,
"round": 1,
"order": 28,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
},
{
"lid": 46,
"round": 1,
"order": 29,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
},
{
"lid": 47,
"round": 1,
"order": 30,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 52,
"round": 2,
"order": 35,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 53,
"round": 2,
"order": 36,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
},
{
"lid": 54,
"round": 2,
"order": 37,
"bye": false,
"home_from_lid": 44,
"home_from_loser": false,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
},
{
"lid": 55,
"round": 2,
"order": 38,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 56,
"round": 3,
"order": 39,
"bye": false,
"home_from_lid": 49,
"home_from_loser": true,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
},
{
"lid": 57,
"round": 3,
"order": 40,
"bye": false,
"home_from_lid": 48,
"home_from_loser": true,
"away_from_lid": 53,
"away_from_loser": false,
"reset": false
},
{
"lid": 58,
"round": 3,
"order": 41,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 54,
"away_from_loser": false,
"reset": false
},
{
"lid": 59,
"round": 3,
"order": 42,
"bye": false,
"home_from_lid": 50,
"home_from_loser": true,
"away_from_lid": 55,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 62,
"round": 4,
"order": 45,
"bye": false,
"home_from_lid": 56,
"home_from_loser": false,
"away_from_lid": 57,
"away_from_loser": false,
"reset": false
},
{
"lid": 63,
"round": 4,
"order": 46,
"bye": false,
"home_from_lid": 58,
"home_from_loser": false,
"away_from_lid": 59,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 64,
"round": 5,
"order": 47,
"bye": false,
"home_from_lid": 60,
"home_from_loser": true,
"away_from_lid": 62,
"away_from_loser": false,
"reset": false
},
{
"lid": 65,
"round": 5,
"order": 48,
"bye": false,
"home_from_lid": 61,
"home_from_loser": true,
"away_from_lid": 63,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 67,
"round": 6,
"order": 50,
"bye": false,
"home_from_lid": 64,
"home_from_loser": false,
"away_from_lid": 65,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 68,
"round": 7,
"order": 51,
"bye": false,
"home_from_lid": 66,
"home_from_loser": true,
"away_from_lid": 67,
"away_from_loser": false,
"reset": false
}
]
]
}

795
brackets/27.json Normal file
View File

@@ -0,0 +1,795 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 16,
"away_seed": 17,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 25,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 3,
"round": 0,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 9,
"away_seed": 24,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 5,
"round": 0,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 13,
"away_seed": 20,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 7,
"round": 0,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 12,
"away_seed": 21,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 8,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 9,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 15,
"away_seed": 18,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 0,
"order": 7,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7,
"away_seed": 26,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 11,
"round": 0,
"order": 8,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 10,
"away_seed": 23,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 13,
"round": 0,
"order": 9,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 14,
"away_seed": 19,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": 10,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6,
"away_seed": 27,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 15,
"round": 0,
"order": 11,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 11,
"away_seed": 22,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 16,
"round": 1,
"order": 15,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 16,
"bye": false,
"home_from_lid": 2,
"home_from_loser": false,
"away_from_lid": 3,
"away_from_loser": false,
"reset": false
},
{
"lid": 18,
"round": 1,
"order": 17,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_from_lid": 5,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 1,
"order": 18,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5,
"away_from_lid": 7,
"away_from_loser": false,
"reset": false
},
{
"lid": 20,
"round": 1,
"order": 19,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 1,
"order": 20,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
},
{
"lid": 22,
"round": 1,
"order": 21,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 23,
"round": 1,
"order": 22,
"bye": false,
"home_from_lid": 14,
"home_from_loser": false,
"away_from_lid": 15,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 48,
"round": 2,
"order": 34,
"bye": false,
"home_from_lid": 16,
"home_from_loser": false,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
},
{
"lid": 49,
"round": 2,
"order": 35,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
},
{
"lid": 50,
"round": 2,
"order": 36,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
},
{
"lid": 51,
"round": 2,
"order": 37,
"bye": false,
"home_from_lid": 22,
"home_from_loser": false,
"away_from_lid": 23,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 60,
"round": 3,
"order": 46,
"bye": false,
"home_from_lid": 48,
"home_from_loser": false,
"away_from_lid": 49,
"away_from_loser": false,
"reset": false
},
{
"lid": 61,
"round": 3,
"order": 47,
"bye": false,
"home_from_lid": 50,
"home_from_loser": false,
"away_from_lid": 51,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 66,
"round": 4,
"order": 52,
"bye": false,
"home_from_lid": 60,
"home_from_loser": false,
"away_from_lid": 61,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 69,
"round": 5,
"order": 55,
"bye": false,
"home_from_lid": 66,
"home_from_loser": false,
"away_from_lid": 68,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 70,
"round": 6,
"order": 56,
"bye": false,
"home_from_lid": 69,
"home_from_loser": false,
"away_from_lid": 69,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 24,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 23,
"home_from_loser": true
},
{
"lid": 25,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 1,
"home_from_loser": true
},
{
"lid": 26,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 22,
"home_from_loser": true
},
{
"lid": 27,
"round": 0,
"order": 23,
"bye": false,
"home_from_lid": 2,
"home_from_loser": true,
"away_from_lid": 3,
"away_from_loser": true,
"reset": false
},
{
"lid": 28,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 21,
"home_from_loser": true
},
{
"lid": 29,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 5,
"home_from_loser": true
},
{
"lid": 30,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 20,
"home_from_loser": true
},
{
"lid": 31,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 7,
"home_from_loser": true
},
{
"lid": 32,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 19,
"home_from_loser": true
},
{
"lid": 33,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 34,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 18,
"home_from_loser": true
},
{
"lid": 35,
"round": 0,
"order": 24,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 11,
"away_from_loser": true,
"reset": false
},
{
"lid": 36,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 17,
"home_from_loser": true
},
{
"lid": 37,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 13,
"home_from_loser": true
},
{
"lid": 38,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 16,
"home_from_loser": true
},
{
"lid": 39,
"round": 0,
"order": 25,
"bye": false,
"home_from_lid": 14,
"home_from_loser": true,
"away_from_lid": 15,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 40,
"round": 1,
"order": 26,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 41,
"round": 1,
"order": 27,
"bye": false,
"home_from_lid": 22,
"home_from_loser": true,
"away_from_lid": 27,
"away_from_loser": false,
"reset": false
},
{
"lid": 42,
"round": 1,
"order": 28,
"bye": false,
"home_from_lid": 21,
"home_from_loser": true,
"away_from_lid": 5,
"away_from_loser": true,
"reset": false
},
{
"lid": 43,
"round": 1,
"order": 29,
"bye": false,
"home_from_lid": 20,
"home_from_loser": true,
"away_from_lid": 7,
"away_from_loser": true,
"reset": false
},
{
"lid": 44,
"round": 1,
"order": 30,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 9,
"away_from_loser": true,
"reset": false
},
{
"lid": 45,
"round": 1,
"order": 31,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 35,
"away_from_loser": false,
"reset": false
},
{
"lid": 46,
"round": 1,
"order": 32,
"bye": false,
"home_from_lid": 17,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": true,
"reset": false
},
{
"lid": 47,
"round": 1,
"order": 33,
"bye": false,
"home_from_lid": 16,
"home_from_loser": true,
"away_from_lid": 39,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 52,
"round": 2,
"order": 38,
"bye": false,
"home_from_lid": 40,
"home_from_loser": false,
"away_from_lid": 41,
"away_from_loser": false,
"reset": false
},
{
"lid": 53,
"round": 2,
"order": 39,
"bye": false,
"home_from_lid": 42,
"home_from_loser": false,
"away_from_lid": 43,
"away_from_loser": false,
"reset": false
},
{
"lid": 54,
"round": 2,
"order": 40,
"bye": false,
"home_from_lid": 44,
"home_from_loser": false,
"away_from_lid": 45,
"away_from_loser": false,
"reset": false
},
{
"lid": 55,
"round": 2,
"order": 41,
"bye": false,
"home_from_lid": 46,
"home_from_loser": false,
"away_from_lid": 47,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 56,
"round": 3,
"order": 42,
"bye": false,
"home_from_lid": 49,
"home_from_loser": true,
"away_from_lid": 52,
"away_from_loser": false,
"reset": false
},
{
"lid": 57,
"round": 3,
"order": 43,
"bye": false,
"home_from_lid": 48,
"home_from_loser": true,
"away_from_lid": 53,
"away_from_loser": false,
"reset": false
},
{
"lid": 58,
"round": 3,
"order": 44,
"bye": false,
"home_from_lid": 51,
"home_from_loser": true,
"away_from_lid": 54,
"away_from_loser": false,
"reset": false
},
{
"lid": 59,
"round": 3,
"order": 45,
"bye": false,
"home_from_lid": 50,
"home_from_loser": true,
"away_from_lid": 55,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 62,
"round": 4,
"order": 48,
"bye": false,
"home_from_lid": 56,
"home_from_loser": false,
"away_from_lid": 57,
"away_from_loser": false,
"reset": false
},
{
"lid": 63,
"round": 4,
"order": 49,
"bye": false,
"home_from_lid": 58,
"home_from_loser": false,
"away_from_lid": 59,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 64,
"round": 5,
"order": 50,
"bye": false,
"home_from_lid": 60,
"home_from_loser": true,
"away_from_lid": 62,
"away_from_loser": false,
"reset": false
},
{
"lid": 65,
"round": 5,
"order": 51,
"bye": false,
"home_from_lid": 61,
"home_from_loser": true,
"away_from_lid": 63,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 67,
"round": 6,
"order": 53,
"bye": false,
"home_from_lid": 64,
"home_from_loser": false,
"away_from_lid": 65,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 68,
"round": 7,
"order": 54,
"bye": false,
"home_from_lid": 66,
"home_from_loser": true,
"away_from_lid": 67,
"away_from_loser": false,
"reset": false
}
]
]
}

311
brackets/9.json Normal file
View File

@@ -0,0 +1,311 @@
{
"winners": [
[
{
"lid": 0,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1
},
{
"lid": 1,
"round": 0,
"order": 1,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 8,
"away_seed": 9,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 2,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4
},
{
"lid": 3,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 5
},
{
"lid": 4,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2
},
{
"lid": 5,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 7
},
{
"lid": 6,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3
},
{
"lid": 7,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 6
}
],
[
{
"lid": 8,
"round": 1,
"order": 5,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 1,
"away_from_lid": 1,
"away_from_loser": false,
"reset": false
},
{
"lid": 9,
"round": 1,
"order": 2,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 4,
"away_seed": 5,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 10,
"round": 1,
"order": 3,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 2,
"away_seed": 7,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
},
{
"lid": 11,
"round": 1,
"order": 4,
"bye": false,
"home_from_lid": null,
"home_from_loser": null,
"home_seed": 3,
"away_seed": 6,
"away_from_lid": null,
"away_from_loser": null,
"reset": false
}
],
[
{
"lid": 18,
"round": 2,
"order": 9,
"bye": false,
"home_from_lid": 8,
"home_from_loser": false,
"away_from_lid": 9,
"away_from_loser": false,
"reset": false
},
{
"lid": 19,
"round": 2,
"order": 10,
"bye": false,
"home_from_lid": 10,
"home_from_loser": false,
"away_from_lid": 11,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 23,
"round": 3,
"order": 14,
"bye": false,
"home_from_lid": 18,
"home_from_loser": false,
"away_from_lid": 19,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 25,
"round": 4,
"order": 16,
"bye": false,
"home_from_lid": 23,
"home_from_loser": false,
"away_from_lid": 24,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 26,
"round": 5,
"order": 17,
"bye": false,
"home_from_lid": 25,
"home_from_loser": false,
"away_from_lid": 25,
"away_from_loser": true,
"reset": true
}
]
],
"losers": [
[
{
"lid": 12,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 10,
"home_from_loser": true
},
{
"lid": 13,
"round": 0,
"order": 6,
"bye": false,
"home_from_lid": 11,
"home_from_loser": true,
"away_from_lid": 1,
"away_from_loser": true,
"reset": false
},
{
"lid": 14,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 9,
"home_from_loser": true
},
{
"lid": 15,
"round": 0,
"order": null,
"bye": true,
"home_from_lid": 8,
"home_from_loser": true
}
],
[
{
"lid": 16,
"round": 1,
"order": 7,
"bye": false,
"home_from_lid": 10,
"home_from_loser": true,
"away_from_lid": 13,
"away_from_loser": false,
"reset": false
},
{
"lid": 17,
"round": 1,
"order": 8,
"bye": false,
"home_from_lid": 9,
"home_from_loser": true,
"away_from_lid": 8,
"away_from_loser": true,
"reset": false
}
],
[
{
"lid": 20,
"round": 2,
"order": 11,
"bye": false,
"home_from_lid": 18,
"home_from_loser": true,
"away_from_lid": 16,
"away_from_loser": false,
"reset": false
},
{
"lid": 21,
"round": 2,
"order": 12,
"bye": false,
"home_from_lid": 19,
"home_from_loser": true,
"away_from_lid": 17,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 22,
"round": 3,
"order": 13,
"bye": false,
"home_from_lid": 20,
"home_from_loser": false,
"away_from_lid": 21,
"away_from_loser": false,
"reset": false
}
],
[
{
"lid": 24,
"round": 4,
"order": 15,
"bye": false,
"home_from_lid": 23,
"home_from_loser": true,
"away_from_lid": 22,
"away_from_loser": false,
"reset": false
}
]
]
}

1245
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -32,17 +32,17 @@ services:
- app-network
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
#redis:
# image: redis:7-alpine
# container_name: redis-cache
# ports:
# - "6379:6379"
# command: redis-server --appendonly yes
# volumes:
# - redis-data:/data
# networks:
# - app-network
# restart: unless-stopped
supertokens:
image: registry.supertokens.io/supertokens/supertokens-postgresql
@@ -51,6 +51,7 @@ services:
- postgres
environment:
POSTGRESQL_CONNECTION_URI: postgresql://supertokens:password@postgres:5432/supertokens
ACCESS_TOKEN_VALIDITY: 360000
ports:
- "3567:3567"
env_file:

View File

@@ -6,7 +6,8 @@
"scripts": {
"dev": "vite dev --host 0.0.0.0",
"build": "vite build && tsc --noEmit",
"start": "vite start"
"start": "bun run .output/server/index.mjs",
"start:node": "node .output/server/index.mjs"
},
"dependencies": {
"@hello-pangea/dnd": "^18.0.1",
@@ -18,13 +19,21 @@
"@mantine/hooks": "^8.2.4",
"@mantine/tiptap": "^8.2.4",
"@phosphor-icons/react": "^2.1.10",
"@svgmoji/noto": "^3.2.0",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"@tanstack/react-router": "^1.130.12",
"@tanstack/react-router-devtools": "^1.130.13",
"@tanstack/react-router-with-query": "^1.130.12",
"@tanstack/react-start": "^1.130.15",
"@tanstack/react-start": "^1.132.2",
"@tanstack/react-virtual": "^3.13.12",
"@tiptap/pm": "^3.4.3",
"@tiptap/react": "^3.4.3",
"@tiptap/starter-kit": "^3.4.3",
"@types/bun": "^1.2.22",
"@types/ioredis": "^4.28.10",
"browser-image-compression": "^2.0.2",
"dotenv": "^17.2.2",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.23.12",
"ioredis": "^5.7.0",
@@ -33,6 +42,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-imask": "^7.6.1",
"react-scan": "^0.4.3",
"react-use-draggable-scroll": "^0.4.7",
"recharts": "^3.1.2",
"redaxios": "^0.5.1",
@@ -45,6 +55,8 @@
"zustand": "^5.0.7"
},
"devDependencies": {
"@tanstack/react-router-ssr-query": "^1.132.2",
"@tanstack/router-plugin": "^1.132.2",
"@types/node": "^22.5.4",
"@types/pg": "^8.15.5",
"@types/react": "^19.0.8",
@@ -57,7 +69,7 @@
"postcss-simple-vars": "^7.0.1",
"tsx": "^4.20.3",
"typescript": "^5.7.2",
"vite": "^6.3.5",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}
}

View File

@@ -0,0 +1,44 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// add field
collection.fields.addAt(18, new Field({
"hidden": false,
"id": "number1705071305",
"max": null,
"min": null,
"name": "home_seed",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// add field
collection.fields.addAt(19, new Field({
"hidden": false,
"id": "number3588777624",
"max": null,
"min": null,
"name": "away_seed",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// remove field
collection.fields.removeById("number1705071305")
// remove field
collection.fields.removeById("number3588777624")
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// add field
collection.fields.addAt(10, new Field({
"cascadeDelete": false,
"collectionId": "pbc_2541054544",
"hidden": false,
"id": "relation103159226",
"maxSelect": 999,
"minSelect": 0,
"name": "matches",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// remove field
collection.fields.removeById("relation103159226")
return app.save(collection)
})

View File

@@ -0,0 +1,27 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// add field
collection.fields.addAt(20, new Field({
"hidden": false,
"id": "number3320769076",
"max": null,
"min": null,
"name": "round",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// remove field
collection.fields.removeById("number3320769076")
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1568971955")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1568971955")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,31 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// add field
collection.fields.addAt(21, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"tbd",
"ready",
"started",
"ended"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// remove field
collection.fields.removeById("select2063623452")
return app.save(collection)
})

View File

@@ -0,0 +1,235 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation2582050271",
"maxSelect": 1,
"minSelect": 0,
"name": "player_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "json4231605813",
"maxSize": 1,
"name": "player_name",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_976G",
"max": 0,
"min": 0,
"name": "first_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_njLe",
"max": 0,
"min": 0,
"name": "last_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_ZNMy",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_340646327",
"hidden": false,
"id": "relation869376999",
"maxSelect": 1,
"minSelect": 0,
"name": "tournament_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_nxTv",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number103159226",
"max": null,
"min": null,
"name": "matches",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "json2732118329",
"maxSize": 1,
"name": "wins",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json724428801",
"maxSize": 1,
"name": "losses",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3041953980",
"maxSize": 1,
"name": "margin_of_victory",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1531431708",
"maxSize": 1,
"name": "margin_of_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1062535948",
"maxSize": 1,
"name": "total_cups_won_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json4249694556",
"maxSize": 1,
"name": "total_cups_lost_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3154249934",
"maxSize": 1,
"name": "total_cups_made",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3227208027",
"maxSize": 1,
"name": "total_cups_against",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_135889471",
"indexes": [],
"listRule": null,
"name": "player_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "\n SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n p.first_name,\n p.last_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_won_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_lost_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p\n JOIN teams t ON json_extract(t.players, '$[*]') LIKE '%' || p.id || '%'\n JOIN matches m ON (m.home = t.id OR m.away = t.id)\n JOIN tournaments tour ON m.tournament = tour.id\n WHERE m.status = 'ended'\n GROUP BY p.id, t.id, tour.id",
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471");
return app.delete(collection);
})

View File

@@ -0,0 +1,194 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471")
// update collection data
unmarshal({
"viewQuery": "SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id, tour.id"
}, collection)
// remove field
collection.fields.removeById("_clone_976G")
// remove field
collection.fields.removeById("_clone_njLe")
// remove field
collection.fields.removeById("_clone_ZNMy")
// remove field
collection.fields.removeById("_clone_nxTv")
// remove field
collection.fields.removeById("json3041953980")
// remove field
collection.fields.removeById("json1531431708")
// remove field
collection.fields.removeById("json1062535948")
// remove field
collection.fields.removeById("json4249694556")
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_YqC8",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_jZTo",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471")
// update collection data
unmarshal({
"viewQuery": "\n SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n p.first_name,\n p.last_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_won_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_lost_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p\n JOIN teams t ON json_extract(t.players, '$[*]') LIKE '%' || p.id || '%'\n JOIN matches m ON (m.home = t.id OR m.away = t.id)\n JOIN tournaments tour ON m.tournament = tour.id\n WHERE m.status = 'ended'\n GROUP BY p.id, t.id, tour.id"
}, collection)
// add field
collection.fields.addAt(3, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_976G",
"max": 0,
"min": 0,
"name": "first_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_njLe",
"max": 0,
"min": 0,
"name": "last_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_ZNMy",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(8, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_nxTv",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(12, new Field({
"hidden": false,
"id": "json3041953980",
"maxSize": 1,
"name": "margin_of_victory",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(13, new Field({
"hidden": false,
"id": "json1531431708",
"maxSize": 1,
"name": "margin_of_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(14, new Field({
"hidden": false,
"id": "json1062535948",
"maxSize": 1,
"name": "total_cups_won_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(15, new Field({
"hidden": false,
"id": "json4249694556",
"maxSize": 1,
"name": "total_cups_lost_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// remove field
collection.fields.removeById("_clone_YqC8")
// remove field
collection.fields.removeById("_clone_jZTo")
return app.save(collection)
})

View File

@@ -0,0 +1,96 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471")
// update collection data
unmarshal({
"name": "player_stats_per_tournament"
}, collection)
// remove field
collection.fields.removeById("_clone_YqC8")
// remove field
collection.fields.removeById("_clone_jZTo")
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_XGbN",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_uud6",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471")
// update collection data
unmarshal({
"name": "player_stats"
}, collection)
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_YqC8",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_jZTo",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// remove field
collection.fields.removeById("_clone_XGbN")
// remove field
collection.fields.removeById("_clone_uud6")
return app.save(collection)
})

View File

@@ -0,0 +1,180 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation2582050271",
"maxSelect": 1,
"minSelect": 0,
"name": "player_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "json4231605813",
"maxSize": 1,
"name": "player_name",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_fQu1",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number103159226",
"max": null,
"min": null,
"name": "matches",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "json2732118329",
"maxSize": 1,
"name": "wins",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json724428801",
"maxSize": 1,
"name": "losses",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3154249934",
"maxSize": 1,
"name": "total_cups_made",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3227208027",
"maxSize": 1,
"name": "total_cups_against",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json2379943496",
"maxSize": 1,
"name": "win_percentage",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3165107022",
"maxSize": 1,
"name": "avg_cups_per_match",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3041953980",
"maxSize": 1,
"name": "margin_of_victory",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1531431708",
"maxSize": 1,
"name": "margin_of_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_1358894712",
"indexes": [],
"listRule": null,
"name": "player_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT\n (p.id || '_' || t.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Additional calculated stats\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin calculations\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id",
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1358894712");
return app.delete(collection);
})

View File

@@ -0,0 +1,57 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1358894712")
// update collection data
unmarshal({
"viewQuery": "SELECT\n p.id as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Win percentage\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n -- Average cups per match\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin of Victory\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END), 2) as margin_of_victory,\n -- Margin of Loss\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END), 2) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id"
}, collection)
// remove field
collection.fields.removeById("relation694999214")
// remove field
collection.fields.removeById("_clone_fQu1")
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1358894712")
// update collection data
unmarshal({
"viewQuery": "SELECT\n (p.id || '_' || t.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Additional calculated stats\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin calculations\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id"
}, collection)
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_fQu1",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,211 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_ZNMy",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number103159226",
"max": null,
"min": null,
"name": "matches",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "json2732118329",
"maxSize": 1,
"name": "wins",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json724428801",
"maxSize": 1,
"name": "losses",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3041953980",
"maxSize": 1,
"name": "margin_of_victory",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1531431708",
"maxSize": 1,
"name": "margin_of_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1062535948",
"maxSize": 1,
"name": "total_cups_won_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json4249694556",
"maxSize": 1,
"name": "total_cups_lost_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3154249934",
"maxSize": 1,
"name": "total_cups_made",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3227208027",
"maxSize": 1,
"name": "total_cups_against",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_135889472",
"indexes": [],
"listRule": null,
"name": "team_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": `
SELECT
t.id as id,
t.id as team_id,
t.name as team_name,
COUNT(m.id) as matches,
SUM(CASE
WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR
(m.away = t.id AND m.away_cups > m.home_cups)
THEN 1 ELSE 0
END) as wins,
SUM(CASE
WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR
(m.away = t.id AND m.away_cups < m.home_cups)
THEN 1 ELSE 0
END) as losses,
AVG(CASE
WHEN m.home = t.id AND m.home_cups > m.away_cups
THEN m.home_cups - m.away_cups
WHEN m.away = t.id AND m.away_cups > m.home_cups
THEN m.away_cups - m.home_cups
ELSE NULL
END) as margin_of_victory,
AVG(CASE
WHEN m.home = t.id AND m.home_cups < m.away_cups
THEN m.away_cups - m.home_cups
WHEN m.away = t.id AND m.away_cups < m.home_cups
THEN m.home_cups - m.away_cups
ELSE NULL
END) as margin_of_loss,
SUM(CASE
WHEN m.home = t.id THEN m.home_cups
WHEN m.away = t.id THEN m.away_cups
ELSE 0
END) as total_cups_won_by,
SUM(CASE
WHEN m.home = t.id THEN m.away_cups
WHEN m.away = t.id THEN m.home_cups
ELSE 0
END) as total_cups_lost_by,
SUM(CASE
WHEN m.home = t.id THEN m.home_cups
WHEN m.away = t.id THEN m.away_cups
ELSE 0
END) as total_cups_made,
SUM(CASE
WHEN m.home = t.id THEN m.away_cups
WHEN m.away = t.id THEN m.home_cups
ELSE 0
END) as total_cups_against
FROM teams t
JOIN matches m ON (m.home = t.id OR m.away = t.id)
JOIN tournaments tour ON m.tournament = tour.id
WHERE m.status = 'ended'
GROUP BY t.id`,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_135889472");
return app.delete(collection);
})

View File

@@ -0,0 +1,167 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_135889471");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation2582050271",
"maxSelect": 1,
"minSelect": 0,
"name": "player_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "json4231605813",
"maxSize": 1,
"name": "player_name",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_XGbN",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_340646327",
"hidden": false,
"id": "relation869376999",
"maxSelect": 1,
"minSelect": 0,
"name": "tournament_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_uud6",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number103159226",
"max": null,
"min": null,
"name": "matches",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "json2732118329",
"maxSize": 1,
"name": "wins",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json724428801",
"maxSize": 1,
"name": "losses",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3154249934",
"maxSize": 1,
"name": "total_cups_made",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3227208027",
"maxSize": 1,
"name": "total_cups_against",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_135889471",
"indexes": [],
"listRule": null,
"name": "player_stats_per_tournament",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id, tour.id",
"viewRule": null
});
return app.save(collection);
})

View File

@@ -0,0 +1,96 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_135889472")
// update collection data
unmarshal({
"name": "team_stats_per_tournament"
}, collection)
// remove field
collection.fields.removeById("_clone_2Mic")
// remove field
collection.fields.removeById("_clone_C8ev")
// add field
collection.fields.addAt(2, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_QmWG",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_1o7N",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_135889472")
// update collection data
unmarshal({
"name": "team_stats"
}, collection)
// add field
collection.fields.addAt(2, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_2Mic",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(4, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_C8ev",
"max": 0,
"min": 0,
"name": "tournament_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
// remove field
collection.fields.removeById("_clone_QmWG")
// remove field
collection.fields.removeById("_clone_1o7N")
return app.save(collection)
})

View File

@@ -0,0 +1,157 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1568971955",
"hidden": false,
"id": "relation694999214",
"maxSelect": 1,
"minSelect": 0,
"name": "team_id",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_nYJn",
"max": 0,
"min": 0,
"name": "team_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "number103159226",
"max": null,
"min": null,
"name": "matches",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "json2732118329",
"maxSize": 1,
"name": "wins",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json724428801",
"maxSize": 1,
"name": "losses",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3041953980",
"maxSize": 1,
"name": "margin_of_victory",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1531431708",
"maxSize": 1,
"name": "margin_of_loss",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json1062535948",
"maxSize": 1,
"name": "total_cups_won_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json4249694556",
"maxSize": 1,
"name": "total_cups_lost_by",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3154249934",
"maxSize": 1,
"name": "total_cups_made",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json3227208027",
"maxSize": 1,
"name": "total_cups_against",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_1582517110",
"indexes": [],
"listRule": null,
"name": "team_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "\n SELECT\n t.id as id,\n t.id as team_id,\n t.name as team_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_won_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_lost_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM teams t\n JOIN matches m ON (m.home = t.id OR m.away = t.id)\n WHERE m.status = 'ended'\n GROUP BY t.id",
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1582517110");
return app.delete(collection);
})

View File

@@ -0,0 +1,55 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1568971955")
// remove field
collection.fields.removeById("number3356599746")
// update field
collection.fields.addAt(9, new Field({
"hidden": false,
"id": "number1329349942",
"max": null,
"min": null,
"name": "song_end",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1568971955")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "number3356599746",
"max": null,
"min": null,
"name": "song_year",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// update field
collection.fields.addAt(10, new Field({
"hidden": false,
"id": "number1329349942",
"max": null,
"min": null,
"name": "song_duration",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,37 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1358894712")
// update collection data
unmarshal({
"viewQuery": "SELECT\n p.id as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n COUNT(m.id) as matches,\n COUNT(DISTINCT m.tournament) as tournaments,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Win percentage\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n -- Average cups per match\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin of Victory\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END), 2) as margin_of_victory,\n -- Margin of Loss\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END), 2) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id"
}, collection)
// add field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "number3837590211",
"max": null,
"min": null,
"name": "tournaments",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1358894712")
// update collection data
unmarshal({
"viewQuery": "SELECT\n p.id as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Win percentage\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n -- Average cups per match\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin of Victory\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END), 2) as margin_of_victory,\n -- Margin of Loss\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END), 2) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id"
}, collection)
// remove field
collection.fields.removeById("number3837590211")
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// add field
collection.fields.addAt(11, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation1584152981",
"maxSelect": 999,
"minSelect": 0,
"name": "free_agents",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// remove field
collection.fields.removeById("relation1584152981")
return app.save(collection)
})

View File

@@ -0,0 +1,84 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3058431538",
"max": 0,
"min": 0,
"name": "emoji",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation2551806565",
"maxSelect": 1,
"minSelect": 0,
"name": "player",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1549310251",
"indexes": [],
"listRule": null,
"name": "reactions",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1549310251");
return app.delete(collection);
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1549310251")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_2541054544",
"hidden": false,
"id": "relation2052834565",
"maxSelect": 1,
"minSelect": 0,
"name": "match",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1549310251")
// remove field
collection.fields.removeById("relation2052834565")
return app.save(collection)
})

View File

@@ -0,0 +1,85 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1843675174",
"max": 0,
"min": 0,
"name": "description",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1340419796",
"indexes": [],
"listRule": null,
"name": "badges",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1340419796");
return app.delete(collection);
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// add field
collection.fields.addAt(5, new Field({
"cascadeDelete": false,
"collectionId": "pbc_1340419796",
"hidden": false,
"id": "relation2029409178",
"maxSelect": 999,
"minSelect": 0,
"name": "badges",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// remove field
collection.fields.removeById("relation2029409178")
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// add field
collection.fields.addAt(6, new Field({
"cascadeDelete": false,
"collectionId": "pbc_1340419796",
"hidden": false,
"id": "relation2813965191",
"maxSelect": 1,
"minSelect": 0,
"name": "featured_badge",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3072146508")
// remove field
collection.fields.removeById("relation2813965191")
return app.save(collection)
})

View File

@@ -0,0 +1,84 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation2551806565",
"maxSelect": 1,
"minSelect": 0,
"name": "player",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1146066909",
"max": 0,
"min": 0,
"name": "phone",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_2929550049",
"indexes": [],
"listRule": null,
"name": "free_agents",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049");
return app.delete(collection);
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_340646327",
"hidden": false,
"id": "relation3177167065",
"maxSelect": 1,
"minSelect": 0,
"name": "tournament",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// remove field
collection.fields.removeById("relation3177167065")
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// remove field
collection.fields.removeById("relation1584152981")
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_340646327")
// add field
collection.fields.addAt(11, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3072146508",
"hidden": false,
"id": "relation1584152981",
"maxSelect": 999,
"minSelect": 0,
"name": "free_agents",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2929550049")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
})

364
server.ts Normal file
View File

@@ -0,0 +1,364 @@
/**
* TanStack Start Production Server with Bun
*
* A high-performance production server for TanStack Start applications that
* implements intelligent static asset loading with configurable memory management.
*
* Features:
* - Hybrid loading strategy (preload small files, serve large files on-demand)
* - Configurable file filtering with include/exclude patterns
* - Memory-efficient response generation
* - Production-ready caching headers
*
* Environment Variables:
*
* PORT (number)
* - Server port number
* - Default: 3000
*
* STATIC_PRELOAD_MAX_BYTES (number)
* - Maximum file size in bytes to preload into memory
* - Files larger than this will be served on-demand from disk
* - Default: 5242880 (5MB)
* - Example: STATIC_PRELOAD_MAX_BYTES=5242880 (5MB)
*
* STATIC_PRELOAD_INCLUDE (string)
* - Comma-separated list of glob patterns for files to include
* - If specified, only matching files are eligible for preloading
* - Patterns are matched against filenames only, not full paths
* - Example: STATIC_PRELOAD_INCLUDE="*.js,*.css,*.woff2"
*
* STATIC_PRELOAD_EXCLUDE (string)
* - Comma-separated list of glob patterns for files to exclude
* - Applied after include patterns
* - Patterns are matched against filenames only, not full paths
* - Example: STATIC_PRELOAD_EXCLUDE="*.map,*.txt"
*
* STATIC_PRELOAD_VERBOSE (boolean)
* - Enable detailed logging of loaded and skipped files
* - Default: false
* - Set to "true" to enable verbose output
*
* Usage:
* bun run server.ts
*/
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
// Configuration
const PORT = Number(process.env.PORT ?? 3000)
const CLIENT_DIR = './dist/client'
const SERVER_ENTRY = './dist/server/server.js'
// Preloading configuration from environment variables
const MAX_PRELOAD_BYTES = Number(
process.env.STATIC_PRELOAD_MAX_BYTES ?? 5 * 1024 * 1024, // 5MB default
)
// Parse comma-separated include patterns (no defaults)
const INCLUDE_PATTERNS = (process.env.STATIC_PRELOAD_INCLUDE ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map(globToRegExp)
// Parse comma-separated exclude patterns (no defaults)
const EXCLUDE_PATTERNS = (process.env.STATIC_PRELOAD_EXCLUDE ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map(globToRegExp)
// Verbose logging flag
const VERBOSE = process.env.STATIC_PRELOAD_VERBOSE === 'true'
/**
* Convert a simple glob pattern to a regular expression
* Supports * wildcard for matching any characters
*/
function globToRegExp(glob: string): RegExp {
// Escape regex special chars except *, then replace * with .*
const escaped = glob
.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
.replace(/\*/g, '.*')
return new RegExp(`^${escaped}$`, 'i')
}
/**
* Metadata for preloaded static assets
*/
interface AssetMetadata {
route: string
size: number
type: string
}
/**
* Result of static asset preloading process
*/
interface PreloadResult {
routes: Record<string, () => Response>
loaded: Array<AssetMetadata>
skipped: Array<AssetMetadata>
}
/**
* Check if a file should be included based on configured patterns
*/
function shouldInclude(relativePath: string): boolean {
const fileName = relativePath.split(/[/\\]/).pop() ?? relativePath
// If include patterns are specified, file must match at least one
if (INCLUDE_PATTERNS.length > 0) {
if (!INCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
return false
}
}
// If exclude patterns are specified, file must not match any
if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
return false
}
return true
}
/**
* Build static routes with intelligent preloading strategy
* Small files are loaded into memory, large files are served on-demand
*/
async function buildStaticRoutes(clientDir: string): Promise<PreloadResult> {
const routes: Record<string, () => Response> = {}
const loaded: Array<AssetMetadata> = []
const skipped: Array<AssetMetadata> = []
console.log(`📦 Loading static assets from ${clientDir}...`)
console.log(
` Max preload size: ${(MAX_PRELOAD_BYTES / 1024 / 1024).toFixed(2)} MB`,
)
if (INCLUDE_PATTERNS.length > 0) {
console.log(
` Include patterns: ${process.env.STATIC_PRELOAD_INCLUDE ?? ''}`,
)
}
if (EXCLUDE_PATTERNS.length > 0) {
console.log(
` Exclude patterns: ${process.env.STATIC_PRELOAD_EXCLUDE ?? ''}`,
)
}
let totalPreloadedBytes = 0
try {
// Read all files recursively
const files = await readdir(clientDir, { recursive: true })
for (const relativePath of files) {
const filepath = join(clientDir, relativePath)
const route = '/' + relativePath.replace(/\\/g, '/') // Handle Windows paths
try {
// Get file metadata
const file = Bun.file(filepath)
// Skip if file doesn't exist or is empty
if (!(await file.exists()) || file.size === 0) {
continue
}
const metadata: AssetMetadata = {
route,
size: file.size,
type: file.type || 'application/octet-stream',
}
// Determine if file should be preloaded
const matchesPattern = shouldInclude(relativePath)
const withinSizeLimit = file.size <= MAX_PRELOAD_BYTES
if (matchesPattern && withinSizeLimit) {
// Preload small files into memory
const bytes = await file.bytes()
routes[route] = () =>
new Response(bytes, {
headers: {
'Content-Type': metadata.type,
'Cache-Control': 'public, max-age=31536000, immutable',
},
})
loaded.push({ ...metadata, size: bytes.byteLength })
totalPreloadedBytes += bytes.byteLength
} else {
// Serve large or filtered files on-demand
routes[route] = () => {
const fileOnDemand = Bun.file(filepath)
return new Response(fileOnDemand, {
headers: {
'Content-Type': metadata.type,
'Cache-Control': 'public, max-age=3600',
},
})
}
skipped.push(metadata)
}
} catch (error: unknown) {
if (error instanceof Error && error.name !== 'EISDIR') {
console.error(`❌ Failed to load ${filepath}:`, error)
}
}
}
// Always show file overview in Vite-like format first
if (loaded.length > 0 || skipped.length > 0) {
const allFiles = [...loaded, ...skipped].sort((a, b) =>
a.route.localeCompare(b.route),
)
// Calculate max path length for alignment
const maxPathLength = Math.min(
Math.max(...allFiles.map((f) => f.route.length)),
60,
)
// Format file size with KB and gzip estimation
const formatFileSize = (bytes: number) => {
const kb = bytes / 1024
// Rough gzip estimation (typically 30-70% compression)
const gzipKb = kb * 0.35
return {
size: kb < 100 ? kb.toFixed(2) : kb.toFixed(1),
gzip: gzipKb < 100 ? gzipKb.toFixed(2) : gzipKb.toFixed(1),
}
}
if (loaded.length > 0) {
console.log('\n📁 Preloaded into memory:')
loaded
.sort((a, b) => a.route.localeCompare(b.route))
.forEach((file) => {
const { size, gzip } = formatFileSize(file.size)
const paddedPath = file.route.padEnd(maxPathLength)
const sizeStr = `${size.padStart(7)} kB`
const gzipStr = `gzip: ${gzip.padStart(6)} kB`
console.log(` ${paddedPath} ${sizeStr}${gzipStr}`)
})
}
if (skipped.length > 0) {
console.log('\n💾 Served on-demand:')
skipped
.sort((a, b) => a.route.localeCompare(b.route))
.forEach((file) => {
const { size, gzip } = formatFileSize(file.size)
const paddedPath = file.route.padEnd(maxPathLength)
const sizeStr = `${size.padStart(7)} kB`
const gzipStr = `gzip: ${gzip.padStart(6)} kB`
console.log(` ${paddedPath} ${sizeStr}${gzipStr}`)
})
}
// Show detailed verbose info if enabled
if (VERBOSE) {
console.log('\n📊 Detailed file information:')
allFiles.forEach((file) => {
const isPreloaded = loaded.includes(file)
const status = isPreloaded ? '[MEMORY]' : '[ON-DEMAND]'
const reason =
!isPreloaded && file.size > MAX_PRELOAD_BYTES
? ' (too large)'
: !isPreloaded
? ' (filtered)'
: ''
console.log(
` ${status.padEnd(12)} ${file.route} - ${file.type}${reason}`,
)
})
}
}
// Log summary after the file list
console.log() // Empty line for separation
if (loaded.length > 0) {
console.log(
`✅ Preloaded ${String(loaded.length)} files (${(totalPreloadedBytes / 1024 / 1024).toFixed(2)} MB) into memory`,
)
} else {
console.log(' No files preloaded into memory')
}
if (skipped.length > 0) {
const tooLarge = skipped.filter((f) => f.size > MAX_PRELOAD_BYTES).length
const filtered = skipped.length - tooLarge
console.log(
` ${String(skipped.length)} files will be served on-demand (${String(tooLarge)} too large, ${String(filtered)} filtered)`,
)
}
} catch (error) {
console.error(`❌ Failed to load static files from ${clientDir}:`, error)
}
return { routes, loaded, skipped }
}
/**
* Start the production server
*/
async function startServer() {
console.log('🚀 Starting production server...')
// Load TanStack Start server handler
let handler: { fetch: (request: Request) => Response | Promise<Response> }
try {
const serverModule = (await import(SERVER_ENTRY)) as {
default: { fetch: (request: Request) => Response | Promise<Response> }
}
handler = serverModule.default
console.log('✅ TanStack Start handler loaded')
} catch (error) {
console.error('❌ Failed to load server handler:', error)
process.exit(1)
}
// Build static routes with intelligent preloading
const { routes } = await buildStaticRoutes(CLIENT_DIR)
// Create Bun server
const server = Bun.serve({
port: PORT,
routes: {
// Serve static assets (preloaded or on-demand)
...routes,
// Fallback to TanStack Start handler for all other routes
'/*': (request) => {
try {
return handler.fetch(request)
} catch (error) {
console.error('Server handler error:', error)
return new Response('Internal Server Error', { status: 500 })
}
},
},
// Global error handler
error(error) {
console.error('Uncaught server error:', error)
return new Response('Internal Server Error', { status: 500 })
},
})
console.log(
`\n🚀 Server running at http://localhost:${String(server.port)}\n`,
)
}
// Start the server
startServer().catch((error: unknown) => {
console.error('Failed to start server:', error)
process.exit(1)
})

View File

@@ -8,31 +8,43 @@
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createServerRootRoute } from '@tanstack/react-start/server'
import { Route as rootRouteImport } from './routes/__root'
import { Route as RefreshSessionRouteImport } from './routes/refresh-session'
import { Route as LogoutRouteImport } from './routes/logout'
import { Route as LoginRouteImport } from './routes/login'
import { Route as AuthedRouteImport } from './routes/_authed'
import { Route as AuthedIndexRouteImport } from './routes/_authed/index'
import { Route as AuthedStatsRouteImport } from './routes/_authed/stats'
import { Route as AuthedSettingsRouteImport } from './routes/_authed/settings'
import { Route as AuthedAdminRouteImport } from './routes/_authed/admin'
import { Route as AuthedTournamentsIndexRouteImport } from './routes/_authed/tournaments/index'
import { Route as AuthedAdminIndexRouteImport } from './routes/_authed/admin/index'
import { Route as ApiTournamentsUploadLogoRouteImport } from './routes/api/tournaments/upload-logo'
import { Route as ApiTeamsUploadLogoRouteImport } from './routes/api/teams/upload-logo'
import { Route as ApiSpotifyTokenRouteImport } from './routes/api/spotify/token'
import { Route as ApiSpotifySearchRouteImport } from './routes/api/spotify/search'
import { Route as ApiSpotifyResumeRouteImport } from './routes/api/spotify/resume'
import { Route as ApiSpotifyPlaybackRouteImport } from './routes/api/spotify/playback'
import { Route as ApiSpotifyCaptureRouteImport } from './routes/api/spotify/capture'
import { Route as ApiSpotifyCallbackRouteImport } from './routes/api/spotify/callback'
import { Route as ApiEventsSplatRouteImport } from './routes/api/events.$'
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$'
import { Route as AuthedTournamentsTournamentIdRouteImport } from './routes/_authed/tournaments/$tournamentId'
import { Route as AuthedTeamsTeamIdRouteImport } from './routes/_authed/teams.$teamId'
import { Route as AuthedProfilePlayerIdRouteImport } from './routes/_authed/profile.$playerId'
import { Route as AuthedAdminPreviewRouteImport } from './routes/_authed/admin/preview'
import { Route as AuthedAdminTournamentsIndexRouteImport } from './routes/_authed/admin/tournaments/index'
import { Route as AuthedAdminTournamentsIdRouteImport } from './routes/_authed/admin/tournaments/$id'
import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket'
import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index'
import { Route as ApiFilesCollectionRecordIdFileRouteImport } from './routes/api/files/$collection/$recordId/$file'
import { Route as AuthedAdminTournamentsRunIdRouteImport } from './routes/_authed/admin/tournaments/run.$id'
import { ServerRoute as ApiTournamentsUploadLogoServerRouteImport } from './routes/api/tournaments/upload-logo'
import { ServerRoute as ApiEventsSplatServerRouteImport } from './routes/api/events.$'
import { ServerRoute as ApiAuthSplatServerRouteImport } from './routes/api/auth.$'
import { ServerRoute as ApiFilesCollectionRecordIdFileServerRouteImport } from './routes/api/files/$collection/$recordId/$file'
const rootServerRouteImport = createServerRootRoute()
import { Route as AuthedAdminTournamentsIdTeamsRouteImport } from './routes/_authed/admin/tournaments/$id/teams'
const RefreshSessionRoute = RefreshSessionRouteImport.update({
id: '/refresh-session',
path: '/refresh-session',
getParentRoute: () => rootRouteImport,
} as any)
const LogoutRoute = LogoutRouteImport.update({
id: '/logout',
path: '/logout',
@@ -52,6 +64,11 @@ const AuthedIndexRoute = AuthedIndexRouteImport.update({
path: '/',
getParentRoute: () => AuthedRoute,
} as any)
const AuthedStatsRoute = AuthedStatsRouteImport.update({
id: '/stats',
path: '/stats',
getParentRoute: () => AuthedRoute,
} as any)
const AuthedSettingsRoute = AuthedSettingsRouteImport.update({
id: '/settings',
path: '/settings',
@@ -72,6 +89,57 @@ const AuthedAdminIndexRoute = AuthedAdminIndexRouteImport.update({
path: '/',
getParentRoute: () => AuthedAdminRoute,
} as any)
const ApiTournamentsUploadLogoRoute =
ApiTournamentsUploadLogoRouteImport.update({
id: '/api/tournaments/upload-logo',
path: '/api/tournaments/upload-logo',
getParentRoute: () => rootRouteImport,
} as any)
const ApiTeamsUploadLogoRoute = ApiTeamsUploadLogoRouteImport.update({
id: '/api/teams/upload-logo',
path: '/api/teams/upload-logo',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyTokenRoute = ApiSpotifyTokenRouteImport.update({
id: '/api/spotify/token',
path: '/api/spotify/token',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifySearchRoute = ApiSpotifySearchRouteImport.update({
id: '/api/spotify/search',
path: '/api/spotify/search',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyResumeRoute = ApiSpotifyResumeRouteImport.update({
id: '/api/spotify/resume',
path: '/api/spotify/resume',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyPlaybackRoute = ApiSpotifyPlaybackRouteImport.update({
id: '/api/spotify/playback',
path: '/api/spotify/playback',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyCaptureRoute = ApiSpotifyCaptureRouteImport.update({
id: '/api/spotify/capture',
path: '/api/spotify/capture',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSpotifyCallbackRoute = ApiSpotifyCallbackRouteImport.update({
id: '/api/spotify/callback',
path: '/api/spotify/callback',
getParentRoute: () => rootRouteImport,
} as any)
const ApiEventsSplatRoute = ApiEventsSplatRouteImport.update({
id: '/api/events/$',
path: '/api/events/$',
getParentRoute: () => rootRouteImport,
} as any)
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
id: '/api/auth/$',
path: '/api/auth/$',
getParentRoute: () => rootRouteImport,
} as any)
const AuthedTournamentsTournamentIdRoute =
AuthedTournamentsTournamentIdRouteImport.update({
id: '/tournaments/$tournamentId',
@@ -99,195 +167,254 @@ const AuthedAdminTournamentsIndexRoute =
path: '/tournaments/',
getParentRoute: () => AuthedAdminRoute,
} as any)
const AuthedAdminTournamentsIdRoute =
AuthedAdminTournamentsIdRouteImport.update({
id: '/tournaments/$id',
path: '/tournaments/$id',
const AuthedTournamentsIdBracketRoute =
AuthedTournamentsIdBracketRouteImport.update({
id: '/tournaments/$id/bracket',
path: '/tournaments/$id/bracket',
getParentRoute: () => AuthedRoute,
} as any)
const AuthedAdminTournamentsIdIndexRoute =
AuthedAdminTournamentsIdIndexRouteImport.update({
id: '/tournaments/$id/',
path: '/tournaments/$id/',
getParentRoute: () => AuthedAdminRoute,
} as any)
const ApiFilesCollectionRecordIdFileRoute =
ApiFilesCollectionRecordIdFileRouteImport.update({
id: '/api/files/$collection/$recordId/$file',
path: '/api/files/$collection/$recordId/$file',
getParentRoute: () => rootRouteImport,
} as any)
const AuthedAdminTournamentsRunIdRoute =
AuthedAdminTournamentsRunIdRouteImport.update({
id: '/tournaments/run/$id',
path: '/tournaments/run/$id',
getParentRoute: () => AuthedAdminRoute,
} as any)
const ApiTournamentsUploadLogoServerRoute =
ApiTournamentsUploadLogoServerRouteImport.update({
id: '/api/tournaments/upload-logo',
path: '/api/tournaments/upload-logo',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiEventsSplatServerRoute = ApiEventsSplatServerRouteImport.update({
id: '/api/events/$',
path: '/api/events/$',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiAuthSplatServerRoute = ApiAuthSplatServerRouteImport.update({
id: '/api/auth/$',
path: '/api/auth/$',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiFilesCollectionRecordIdFileServerRoute =
ApiFilesCollectionRecordIdFileServerRouteImport.update({
id: '/api/files/$collection/$recordId/$file',
path: '/api/files/$collection/$recordId/$file',
getParentRoute: () => rootServerRouteImport,
const AuthedAdminTournamentsIdTeamsRoute =
AuthedAdminTournamentsIdTeamsRouteImport.update({
id: '/tournaments/$id/teams',
path: '/tournaments/$id/teams',
getParentRoute: () => AuthedAdminRoute,
} as any)
export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/logout': typeof LogoutRoute
'/refresh-session': typeof RefreshSessionRoute
'/admin': typeof AuthedAdminRouteWithChildren
'/settings': typeof AuthedSettingsRoute
'/stats': typeof AuthedStatsRoute
'/': typeof AuthedIndexRoute
'/admin/preview': typeof AuthedAdminPreviewRoute
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/admin/': typeof AuthedAdminIndexRoute
'/tournaments': typeof AuthedTournamentsIndexRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute
}
export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/logout': typeof LogoutRoute
'/refresh-session': typeof RefreshSessionRoute
'/settings': typeof AuthedSettingsRoute
'/stats': typeof AuthedStatsRoute
'/': typeof AuthedIndexRoute
'/admin/preview': typeof AuthedAdminPreviewRoute
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/admin': typeof AuthedAdminIndexRoute
'/tournaments': typeof AuthedTournamentsIndexRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/admin/tournaments/$id': typeof AuthedAdminTournamentsIdIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/_authed': typeof AuthedRouteWithChildren
'/login': typeof LoginRoute
'/logout': typeof LogoutRoute
'/refresh-session': typeof RefreshSessionRoute
'/_authed/admin': typeof AuthedAdminRouteWithChildren
'/_authed/settings': typeof AuthedSettingsRoute
'/_authed/stats': typeof AuthedStatsRoute
'/_authed/': typeof AuthedIndexRoute
'/_authed/admin/preview': typeof AuthedAdminPreviewRoute
'/_authed/profile/$playerId': typeof AuthedProfilePlayerIdRoute
'/_authed/teams/$teamId': typeof AuthedTeamsTeamIdRoute
'/_authed/tournaments/$tournamentId': typeof AuthedTournamentsTournamentIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute
'/api/events/$': typeof ApiEventsSplatRoute
'/api/spotify/callback': typeof ApiSpotifyCallbackRoute
'/api/spotify/capture': typeof ApiSpotifyCaptureRoute
'/api/spotify/playback': typeof ApiSpotifyPlaybackRoute
'/api/spotify/resume': typeof ApiSpotifyResumeRoute
'/api/spotify/search': typeof ApiSpotifySearchRoute
'/api/spotify/token': typeof ApiSpotifyTokenRoute
'/api/teams/upload-logo': typeof ApiTeamsUploadLogoRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoRoute
'/_authed/admin/': typeof AuthedAdminIndexRoute
'/_authed/tournaments/': typeof AuthedTournamentsIndexRoute
'/_authed/admin/tournaments/$id': typeof AuthedAdminTournamentsIdRoute
'/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/_authed/admin/tournaments/': typeof AuthedAdminTournamentsIndexRoute
'/_authed/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
'/_authed/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
'/_authed/admin/tournaments/$id/': typeof AuthedAdminTournamentsIdIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/login'
| '/logout'
| '/refresh-session'
| '/admin'
| '/settings'
| '/stats'
| '/'
| '/admin/preview'
| '/profile/$playerId'
| '/teams/$teamId'
| '/tournaments/$tournamentId'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/admin/'
| '/tournaments'
| '/admin/tournaments/$id'
| '/tournaments/$id/bracket'
| '/admin/tournaments'
| '/admin/tournaments/$id/teams'
| '/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file'
| '/admin/tournaments/$id'
fileRoutesByTo: FileRoutesByTo
to:
| '/login'
| '/logout'
| '/refresh-session'
| '/settings'
| '/stats'
| '/'
| '/admin/preview'
| '/profile/$playerId'
| '/teams/$teamId'
| '/tournaments/$tournamentId'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/admin'
| '/tournaments'
| '/admin/tournaments/$id'
| '/tournaments/$id/bracket'
| '/admin/tournaments'
| '/admin/tournaments/$id/teams'
| '/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file'
| '/admin/tournaments/$id'
id:
| '__root__'
| '/_authed'
| '/login'
| '/logout'
| '/refresh-session'
| '/_authed/admin'
| '/_authed/settings'
| '/_authed/stats'
| '/_authed/'
| '/_authed/admin/preview'
| '/_authed/profile/$playerId'
| '/_authed/teams/$teamId'
| '/_authed/tournaments/$tournamentId'
| '/api/auth/$'
| '/api/events/$'
| '/api/spotify/callback'
| '/api/spotify/capture'
| '/api/spotify/playback'
| '/api/spotify/resume'
| '/api/spotify/search'
| '/api/spotify/token'
| '/api/teams/upload-logo'
| '/api/tournaments/upload-logo'
| '/_authed/admin/'
| '/_authed/tournaments/'
| '/_authed/admin/tournaments/$id'
| '/_authed/tournaments/$id/bracket'
| '/_authed/admin/tournaments/'
| '/_authed/admin/tournaments/$id/teams'
| '/_authed/admin/tournaments/run/$id'
| '/api/files/$collection/$recordId/$file'
| '/_authed/admin/tournaments/$id/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
AuthedRoute: typeof AuthedRouteWithChildren
LoginRoute: typeof LoginRoute
LogoutRoute: typeof LogoutRoute
}
export interface FileServerRoutesByFullPath {
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRoutesByTo {
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport
'/api/auth/$': typeof ApiAuthSplatServerRoute
'/api/events/$': typeof ApiEventsSplatServerRoute
'/api/tournaments/upload-logo': typeof ApiTournamentsUploadLogoServerRoute
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileServerRoute
}
export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths:
| '/api/auth/$'
| '/api/events/$'
| '/api/tournaments/upload-logo'
| '/api/files/$collection/$recordId/$file'
fileServerRoutesByTo: FileServerRoutesByTo
to:
| '/api/auth/$'
| '/api/events/$'
| '/api/tournaments/upload-logo'
| '/api/files/$collection/$recordId/$file'
id:
| '__root__'
| '/api/auth/$'
| '/api/events/$'
| '/api/tournaments/upload-logo'
| '/api/files/$collection/$recordId/$file'
fileServerRoutesById: FileServerRoutesById
}
export interface RootServerRouteChildren {
ApiAuthSplatServerRoute: typeof ApiAuthSplatServerRoute
ApiEventsSplatServerRoute: typeof ApiEventsSplatServerRoute
ApiTournamentsUploadLogoServerRoute: typeof ApiTournamentsUploadLogoServerRoute
ApiFilesCollectionRecordIdFileServerRoute: typeof ApiFilesCollectionRecordIdFileServerRoute
RefreshSessionRoute: typeof RefreshSessionRoute
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
ApiEventsSplatRoute: typeof ApiEventsSplatRoute
ApiSpotifyCallbackRoute: typeof ApiSpotifyCallbackRoute
ApiSpotifyCaptureRoute: typeof ApiSpotifyCaptureRoute
ApiSpotifyPlaybackRoute: typeof ApiSpotifyPlaybackRoute
ApiSpotifyResumeRoute: typeof ApiSpotifyResumeRoute
ApiSpotifySearchRoute: typeof ApiSpotifySearchRoute
ApiSpotifyTokenRoute: typeof ApiSpotifyTokenRoute
ApiTeamsUploadLogoRoute: typeof ApiTeamsUploadLogoRoute
ApiTournamentsUploadLogoRoute: typeof ApiTournamentsUploadLogoRoute
ApiFilesCollectionRecordIdFileRoute: typeof ApiFilesCollectionRecordIdFileRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/refresh-session': {
id: '/refresh-session'
path: '/refresh-session'
fullPath: '/refresh-session'
preLoaderRoute: typeof RefreshSessionRouteImport
parentRoute: typeof rootRouteImport
}
'/logout': {
id: '/logout'
path: '/logout'
@@ -316,6 +443,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedIndexRouteImport
parentRoute: typeof AuthedRoute
}
'/_authed/stats': {
id: '/_authed/stats'
path: '/stats'
fullPath: '/stats'
preLoaderRoute: typeof AuthedStatsRouteImport
parentRoute: typeof AuthedRoute
}
'/_authed/settings': {
id: '/_authed/settings'
path: '/settings'
@@ -344,6 +478,76 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminIndexRouteImport
parentRoute: typeof AuthedAdminRoute
}
'/api/tournaments/upload-logo': {
id: '/api/tournaments/upload-logo'
path: '/api/tournaments/upload-logo'
fullPath: '/api/tournaments/upload-logo'
preLoaderRoute: typeof ApiTournamentsUploadLogoRouteImport
parentRoute: typeof rootRouteImport
}
'/api/teams/upload-logo': {
id: '/api/teams/upload-logo'
path: '/api/teams/upload-logo'
fullPath: '/api/teams/upload-logo'
preLoaderRoute: typeof ApiTeamsUploadLogoRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/token': {
id: '/api/spotify/token'
path: '/api/spotify/token'
fullPath: '/api/spotify/token'
preLoaderRoute: typeof ApiSpotifyTokenRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/search': {
id: '/api/spotify/search'
path: '/api/spotify/search'
fullPath: '/api/spotify/search'
preLoaderRoute: typeof ApiSpotifySearchRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/resume': {
id: '/api/spotify/resume'
path: '/api/spotify/resume'
fullPath: '/api/spotify/resume'
preLoaderRoute: typeof ApiSpotifyResumeRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/playback': {
id: '/api/spotify/playback'
path: '/api/spotify/playback'
fullPath: '/api/spotify/playback'
preLoaderRoute: typeof ApiSpotifyPlaybackRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/capture': {
id: '/api/spotify/capture'
path: '/api/spotify/capture'
fullPath: '/api/spotify/capture'
preLoaderRoute: typeof ApiSpotifyCaptureRouteImport
parentRoute: typeof rootRouteImport
}
'/api/spotify/callback': {
id: '/api/spotify/callback'
path: '/api/spotify/callback'
fullPath: '/api/spotify/callback'
preLoaderRoute: typeof ApiSpotifyCallbackRouteImport
parentRoute: typeof rootRouteImport
}
'/api/events/$': {
id: '/api/events/$'
path: '/api/events/$'
fullPath: '/api/events/$'
preLoaderRoute: typeof ApiEventsSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/api/auth/$': {
id: '/api/auth/$'
path: '/api/auth/$'
fullPath: '/api/auth/$'
preLoaderRoute: typeof ApiAuthSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/_authed/tournaments/$tournamentId': {
id: '/_authed/tournaments/$tournamentId'
path: '/tournaments/$tournamentId'
@@ -379,13 +583,27 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminTournamentsIndexRouteImport
parentRoute: typeof AuthedAdminRoute
}
'/_authed/admin/tournaments/$id': {
id: '/_authed/admin/tournaments/$id'
'/_authed/tournaments/$id/bracket': {
id: '/_authed/tournaments/$id/bracket'
path: '/tournaments/$id/bracket'
fullPath: '/tournaments/$id/bracket'
preLoaderRoute: typeof AuthedTournamentsIdBracketRouteImport
parentRoute: typeof AuthedRoute
}
'/_authed/admin/tournaments/$id/': {
id: '/_authed/admin/tournaments/$id/'
path: '/tournaments/$id'
fullPath: '/admin/tournaments/$id'
preLoaderRoute: typeof AuthedAdminTournamentsIdRouteImport
preLoaderRoute: typeof AuthedAdminTournamentsIdIndexRouteImport
parentRoute: typeof AuthedAdminRoute
}
'/api/files/$collection/$recordId/$file': {
id: '/api/files/$collection/$recordId/$file'
path: '/api/files/$collection/$recordId/$file'
fullPath: '/api/files/$collection/$recordId/$file'
preLoaderRoute: typeof ApiFilesCollectionRecordIdFileRouteImport
parentRoute: typeof rootRouteImport
}
'/_authed/admin/tournaments/run/$id': {
id: '/_authed/admin/tournaments/run/$id'
path: '/tournaments/run/$id'
@@ -393,37 +611,12 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminTournamentsRunIdRouteImport
parentRoute: typeof AuthedAdminRoute
}
}
}
declare module '@tanstack/react-start/server' {
interface ServerFileRoutesByPath {
'/api/tournaments/upload-logo': {
id: '/api/tournaments/upload-logo'
path: '/api/tournaments/upload-logo'
fullPath: '/api/tournaments/upload-logo'
preLoaderRoute: typeof ApiTournamentsUploadLogoServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/events/$': {
id: '/api/events/$'
path: '/api/events/$'
fullPath: '/api/events/$'
preLoaderRoute: typeof ApiEventsSplatServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/auth/$': {
id: '/api/auth/$'
path: '/api/auth/$'
fullPath: '/api/auth/$'
preLoaderRoute: typeof ApiAuthSplatServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/files/$collection/$recordId/$file': {
id: '/api/files/$collection/$recordId/$file'
path: '/api/files/$collection/$recordId/$file'
fullPath: '/api/files/$collection/$recordId/$file'
preLoaderRoute: typeof ApiFilesCollectionRecordIdFileServerRouteImport
parentRoute: typeof rootServerRouteImport
'/_authed/admin/tournaments/$id/teams': {
id: '/_authed/admin/tournaments/$id/teams'
path: '/tournaments/$id/teams'
fullPath: '/admin/tournaments/$id/teams'
preLoaderRoute: typeof AuthedAdminTournamentsIdTeamsRouteImport
parentRoute: typeof AuthedAdminRoute
}
}
}
@@ -431,17 +624,19 @@ declare module '@tanstack/react-start/server' {
interface AuthedAdminRouteChildren {
AuthedAdminPreviewRoute: typeof AuthedAdminPreviewRoute
AuthedAdminIndexRoute: typeof AuthedAdminIndexRoute
AuthedAdminTournamentsIdRoute: typeof AuthedAdminTournamentsIdRoute
AuthedAdminTournamentsIndexRoute: typeof AuthedAdminTournamentsIndexRoute
AuthedAdminTournamentsIdTeamsRoute: typeof AuthedAdminTournamentsIdTeamsRoute
AuthedAdminTournamentsRunIdRoute: typeof AuthedAdminTournamentsRunIdRoute
AuthedAdminTournamentsIdIndexRoute: typeof AuthedAdminTournamentsIdIndexRoute
}
const AuthedAdminRouteChildren: AuthedAdminRouteChildren = {
AuthedAdminPreviewRoute: AuthedAdminPreviewRoute,
AuthedAdminIndexRoute: AuthedAdminIndexRoute,
AuthedAdminTournamentsIdRoute: AuthedAdminTournamentsIdRoute,
AuthedAdminTournamentsIndexRoute: AuthedAdminTournamentsIndexRoute,
AuthedAdminTournamentsIdTeamsRoute: AuthedAdminTournamentsIdTeamsRoute,
AuthedAdminTournamentsRunIdRoute: AuthedAdminTournamentsRunIdRoute,
AuthedAdminTournamentsIdIndexRoute: AuthedAdminTournamentsIdIndexRoute,
}
const AuthedAdminRouteWithChildren = AuthedAdminRoute._addFileChildren(
@@ -451,21 +646,25 @@ const AuthedAdminRouteWithChildren = AuthedAdminRoute._addFileChildren(
interface AuthedRouteChildren {
AuthedAdminRoute: typeof AuthedAdminRouteWithChildren
AuthedSettingsRoute: typeof AuthedSettingsRoute
AuthedStatsRoute: typeof AuthedStatsRoute
AuthedIndexRoute: typeof AuthedIndexRoute
AuthedProfilePlayerIdRoute: typeof AuthedProfilePlayerIdRoute
AuthedTeamsTeamIdRoute: typeof AuthedTeamsTeamIdRoute
AuthedTournamentsTournamentIdRoute: typeof AuthedTournamentsTournamentIdRoute
AuthedTournamentsIndexRoute: typeof AuthedTournamentsIndexRoute
AuthedTournamentsIdBracketRoute: typeof AuthedTournamentsIdBracketRoute
}
const AuthedRouteChildren: AuthedRouteChildren = {
AuthedAdminRoute: AuthedAdminRouteWithChildren,
AuthedSettingsRoute: AuthedSettingsRoute,
AuthedStatsRoute: AuthedStatsRoute,
AuthedIndexRoute: AuthedIndexRoute,
AuthedProfilePlayerIdRoute: AuthedProfilePlayerIdRoute,
AuthedTeamsTeamIdRoute: AuthedTeamsTeamIdRoute,
AuthedTournamentsTournamentIdRoute: AuthedTournamentsTournamentIdRoute,
AuthedTournamentsIndexRoute: AuthedTournamentsIndexRoute,
AuthedTournamentsIdBracketRoute: AuthedTournamentsIdBracketRoute,
}
const AuthedRouteWithChildren =
@@ -475,17 +674,28 @@ const rootRouteChildren: RootRouteChildren = {
AuthedRoute: AuthedRouteWithChildren,
LoginRoute: LoginRoute,
LogoutRoute: LogoutRoute,
RefreshSessionRoute: RefreshSessionRoute,
ApiAuthSplatRoute: ApiAuthSplatRoute,
ApiEventsSplatRoute: ApiEventsSplatRoute,
ApiSpotifyCallbackRoute: ApiSpotifyCallbackRoute,
ApiSpotifyCaptureRoute: ApiSpotifyCaptureRoute,
ApiSpotifyPlaybackRoute: ApiSpotifyPlaybackRoute,
ApiSpotifyResumeRoute: ApiSpotifyResumeRoute,
ApiSpotifySearchRoute: ApiSpotifySearchRoute,
ApiSpotifyTokenRoute: ApiSpotifyTokenRoute,
ApiTeamsUploadLogoRoute: ApiTeamsUploadLogoRoute,
ApiTournamentsUploadLogoRoute: ApiTournamentsUploadLogoRoute,
ApiFilesCollectionRecordIdFileRoute: ApiFilesCollectionRecordIdFileRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
const rootServerRouteChildren: RootServerRouteChildren = {
ApiAuthSplatServerRoute: ApiAuthSplatServerRoute,
ApiEventsSplatServerRoute: ApiEventsSplatServerRoute,
ApiTournamentsUploadLogoServerRoute: ApiTournamentsUploadLogoServerRoute,
ApiFilesCollectionRecordIdFileServerRoute:
ApiFilesCollectionRecordIdFileServerRoute,
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
export const serverRouteTree = rootServerRouteImport
._addFileChildren(rootServerRouteChildren)
._addFileTypes<FileServerRouteTypes>()

View File

@@ -1,11 +1,11 @@
import { QueryClient } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary";
import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config";
export function createRouter() {
export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -18,28 +18,32 @@ export function createRouter() {
},
});
return routerWithQueryClient(
createTanStackRouter({
routeTree,
context: {
queryClient,
auth: undefined!,
header: defaultHeaderConfig,
refresh: [],
withPadding: true,
fullWidth: false,
},
defaultPreload: "intent",
defaultErrorComponent: DefaultCatchBoundary,
scrollRestoration: true,
defaultViewTransition: true,
}),
const router = createTanStackRouter({
routeTree,
context: {
queryClient,
auth: undefined!,
header: defaultHeaderConfig,
refresh: [],
withPadding: true,
fullWidth: false,
},
defaultPreload: "intent",
defaultErrorComponent: DefaultCatchBoundary,
scrollRestoration: true,
defaultViewTransition: false,
});
setupRouterSsrQueryIntegration({
router,
queryClient
);
})
return router;
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
router: ReturnType<typeof getRouter>;
}
}

View File

@@ -1,6 +1,3 @@
import "@mantine/core/styles.css";
import "@mantine/dates/styles.css";
import "@mantine/carousel/styles.css";
import {
HeadContent,
Navigate,
@@ -17,8 +14,12 @@ import Providers from "@/features/core/components/providers";
import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core";
import { HeaderConfig } from "@/features/core/types/header-config";
import { playerQueries } from "@/features/players/queries";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import FullScreenLoader from "@/components/full-screen-loader";
import mantineCssUrl from '@mantine/core/styles.css?url'
import mantineDatesCssUrl from '@mantine/dates/styles.css?url'
import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url'
import mantineTiptapCssUrl from '@mantine/tiptap/styles.css?url'
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
@@ -59,26 +60,45 @@ export const Route = createRootRouteWithContext<{
},
{ rel: "manifest", href: "/site.webmanifest" },
{ rel: "icon", href: "/favicon.ico" },
{ rel: 'stylesheet', href: mantineCssUrl },
{ rel: 'stylesheet', href: mantineCarouselCssUrl },
{ rel: 'stylesheet', href: mantineDatesCssUrl },
{ rel: 'stylesheet', href: mantineTiptapCssUrl }
],
}),
errorComponent: (props) => {
return (
<RootDocument>
<DefaultCatchBoundary {...props} />
<Providers>
<DefaultCatchBoundary {...props} />
</Providers>
</RootDocument>
);
},
component: RootComponent,
notFoundComponent: () => <Navigate to="/" />,
beforeLoad: async ({ context }) => {
// https://github.com/TanStack/router/discussions/3531
const auth = await ensureServerQueryData(
context.queryClient,
playerQueries.auth()
);
return { auth };
beforeLoad: async ({ context, location }) => {
// Skip auth check for refresh-session route to avoid infinite loops
if (location.pathname === '/refresh-session') {
return {};
}
if (location.pathname === '/login' || location.pathname === '/logout') {
return {};
}
try {
// https://github.com/TanStack/router/discussions/3531
const auth = await ensureServerQueryData(
context.queryClient,
playerQueries.auth()
);
return { auth };
} catch (error) {
return {};
}
},
pendingComponent: () => <p>Loading...</p>,
pendingComponent: () => <Providers><FullScreenLoader /></Providers>,
});
function RootComponent() {
@@ -122,7 +142,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
>
<div className="app">{children}</div>
<Scripts />
<ReactQueryDevtools />
</body>
</html>
);

View File

@@ -1,7 +1,7 @@
import { redirect, createFileRoute, Outlet } from "@tanstack/react-router";
import Layout from "@/features/core/components/layout";
import { useServerEvents } from "@/hooks/use-server-events";
import { Loader } from "@mantine/core";
import { Flex, Loader } from "@mantine/core";
export const Route = createFileRoute("/_authed")({
beforeLoad: ({ context }) => {
@@ -26,7 +26,9 @@ export const Route = createFileRoute("/_authed")({
},
pendingComponent: () => (
<Layout>
<Loader size="xl" />
<Flex w='100%' h="40dvh" justify="center" align="flex-end">
<Loader size='xl' />
</Flex>
</Layout>
),
});

View File

@@ -1,5 +1,7 @@
import { PreviewBracket } from "@/features/bracket/components/preview";
import BracketPreview from "@/features/admin/components/preview";
import { NumberInput } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
export const Route = createFileRoute("/_authed/admin/preview")({
component: RouteComponent,
@@ -9,9 +11,25 @@ export const Route = createFileRoute("/_authed/admin/preview")({
title: "Bracket Preview",
},
withPadding: false,
fullWidth: true,
}),
});
function RouteComponent() {
return <PreviewBracket />;
const [n, setN] = useState(16);
return (
<>
<NumberInput
min={9}
max={27}
label="Number of teams"
value={n}
onChange={(value) => setN(value as number)}
w={150}
mb={2}
mx="md"
/>
<BracketPreview n={n} />
</>
);
}

View File

@@ -3,7 +3,7 @@ import { tournamentQueries } from "@/features/tournaments/queries";
import ManageTournament from "@/features/tournaments/components/manage-tournament";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
export const Route = createFileRoute("/_authed/admin/tournaments/$id")({
export const Route = createFileRoute("/_authed/admin/tournaments/$id/")({
beforeLoad: async ({ context, params }) => {
const { queryClient } = context;
const tournament = await ensureServerQueryData(

View File

@@ -0,0 +1,32 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { tournamentQueries } from "@/features/tournaments/queries";
import ManageTeams from "@/features/teams/components/manage-teams";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
export const Route = createFileRoute("/_authed/admin/tournaments/$id/teams")({
beforeLoad: async ({ context, params }) => {
const { queryClient } = context;
const tournament = await ensureServerQueryData(
queryClient,
tournamentQueries.details(params.id)
);
if (!tournament) throw redirect({ to: "/admin/tournaments" });
return {
tournament,
};
},
loader: ({ context }) => ({
header: {
withBackButton: true,
title: `${context.tournament.name} Teams`,
},
withPadding: false,
}),
component: RouteComponent,
});
function RouteComponent() {
const { id } = Route.useParams();
const { tournament } = Route.useRouteContext();
return <ManageTeams tournament={tournament} />;
}

View File

@@ -1,31 +1,90 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
import { tournamentQueries } from '@/features/tournaments/queries'
import { ensureServerQueryData } from '@/lib/tanstack-query/utils/ensure'
import RunTournament from '@/features/tournaments/components/run-tournament'
import { createFileRoute, redirect } from "@tanstack/react-router";
import {
tournamentQueries,
useTournament,
} from "@/features/tournaments/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import SeedTournament from "@/features/tournaments/components/seed-tournament";
import { Container } from "@mantine/core";
import { useMemo } from "react";
import { BracketData } from "@/features/bracket/types";
import { Match } from "@/features/matches/types";
import BracketView from "@/features/bracket/components/bracket-view";
import { SpotifyControlsBar } from "@/features/spotify/components";
export const Route = createFileRoute('/_authed/admin/tournaments/run/$id')({
export const Route = createFileRoute("/_authed/admin/tournaments/run/$id")({
beforeLoad: async ({ context, params }) => {
const { queryClient } = context
const { queryClient } = context;
const tournament = await ensureServerQueryData(
queryClient,
tournamentQueries.details(params.id)
)
if (!tournament) throw redirect({ to: '/admin/tournaments' })
);
if (!tournament) throw redirect({ to: "/admin/tournaments" });
return {
tournament,
}
};
},
loader: ({ context }) => ({
fullWidth: true,
withPadding: false,
showSpotifyPanel: true,
header: {
withBackButton: true,
title: `Run ${context.tournament.name}`,
},
}),
component: RouteComponent,
})
});
function RouteComponent() {
const { id } = Route.useParams()
return <RunTournament tournamentId={id} />
const { id } = Route.useParams();
const { data: tournament } = useTournament(id);
const bracket: BracketData = useMemo(() => {
if (!tournament.matches || tournament.matches.length === 0) {
return { winners: [], losers: [] };
}
const winnersMap = new Map<number, Match[]>();
const losersMap = new Map<number, Match[]>();
tournament.matches
.sort((a, b) => a.lid - b.lid)
.forEach((match) => {
if (!match.is_losers_bracket) {
if (!winnersMap.has(match.round)) {
winnersMap.set(match.round, []);
}
winnersMap.get(match.round)!.push(match);
} else {
if (!losersMap.has(match.round)) {
losersMap.set(match.round, []);
}
losersMap.get(match.round)!.push(match);
}
});
const winners = Array.from(winnersMap.entries())
.sort(([a], [b]) => a - b)
.map(([, matches]) => matches);
const losers = Array.from(losersMap.entries())
.sort(([a], [b]) => a - b)
.map(([, matches]) => matches);
return { winners, losers };
}, [tournament.matches]);
return (
<Container size="md" px={0}>
<SpotifyControlsBar />
{tournament.matches?.length ? (
<BracketView bracket={bracket} showControls />
) : (
<SeedTournament
tournamentId={tournament.id}
teams={tournament.teams || []}
/>
)}
</Container>
);
}

View File

@@ -1,27 +1,35 @@
import { createFileRoute } from "@tanstack/react-router";
import { TrophyIcon } from "@phosphor-icons/react";
import ListLink from "@/components/list-link";
import { Box, Divider, Text } from "@mantine/core";
import { tournamentQueries, useCurrentTournament } from "@/features/tournaments/queries";
import UpcomingTournament from "@/features/tournaments/components/upcoming-tournament";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import StartedTournament from "@/features/tournaments/components/started-tournament";
import { Suspense } from "react";
import UpcomingTournamentSkeleton from "@/features/tournaments/components/upcoming-tournament/skeleton";
export const Route = createFileRoute("/_authed/")({
component: Home,
loader: () => ({
withPadding: false
})
component: () => <Suspense fallback={<UpcomingTournamentSkeleton />}>
<Home />
</Suspense>,
beforeLoad: async ({ context }) => {
const queryClient = context.queryClient;
const tournament = await ensureServerQueryData(queryClient, tournamentQueries.current())
return { tournament }
},
loader: ({ context }) => ({
withPadding: false,
header: {
title: context.tournament.name || "FLXN"
}
}),
pendingComponent: () => <UpcomingTournamentSkeleton />
});
function Home() {
return (
<>
<Box h='60vh' p="md">
<Text m='16vh' fw={500}>Some Content Here</Text>
</Box>
const { data: tournament } = useCurrentTournament();
if (!tournament.matches || tournament.matches.length === 0) {
return <UpcomingTournament tournament={tournament} />;
}
<Box>
<Text pl='md'>Quick Links</Text>
<Divider />
<ListLink label="All Tournaments" to="/tournaments" Icon={TrophyIcon} />
</Box>
</>
);
return <StartedTournament tournament={tournament} />
}

View File

@@ -1,7 +1,10 @@
import Profile from "@/features/players/components/profile";
import { playerQueries } from "@/features/players/queries";
import HeaderSkeleton from "@/features/players/components/profile/header-skeleton";
import ProfileSkeleton from "@/features/players/components/profile/skeleton";
import { playerKeys, playerQueries } from "@/features/players/queries";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { createFileRoute } from "@tanstack/react-router";
import { Suspense } from "react";
import { z } from "zod";
const searchSchema = z.object({
@@ -12,10 +15,16 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
validateSearch: searchSchema,
beforeLoad: async ({ params, context }) => {
const { queryClient } = context;
await prefetchServerQuery(
queryClient,
playerQueries.details(params.playerId)
);
await Promise.all([
prefetchServerQuery(
queryClient,
playerQueries.details(params.playerId)
),
prefetchServerQuery(
queryClient,
playerQueries.matches(params.playerId)
),
]);
},
loader: ({ params, context }) => ({
header: {
@@ -24,10 +33,13 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
settingsLink:
context?.auth.user.id === params.playerId ? "/settings" : undefined,
},
refresh: [playerQueries.details(params.playerId).queryKey],
withPadding: false,
refresh: [playerKeys.details(params.playerId), playerKeys.matches(params.playerId), playerKeys.stats(params.playerId)],
}),
component: () => {
const { playerId } = Route.useParams();
return <Profile id={playerId} />;
return <Suspense fallback={<ProfileSkeleton />}>
<Profile id={playerId} />
</Suspense>;
},
});

View File

@@ -0,0 +1,28 @@
import { createFileRoute } from "@tanstack/react-router";
import { playerQueries } from "@/features/players/queries";
import PlayerStatsTable from "@/features/players/components/player-stats-table";
import { Suspense } from "react";
import PlayerStatsTableSkeleton from "@/features/players/components/player-stats-table-skeleton";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
export const Route = createFileRoute("/_authed/stats")({
component: Stats,
beforeLoad: ({ context }) => {
const queryClient = context.queryClient;
prefetchServerQuery(queryClient, playerQueries.allStats());
},
loader: () => ({
withPadding: false,
fullWidth: true,
header: {
title: "Player Stats"
},
refresh: [playerQueries.allStats().queryKey],
}),
});
function Stats() {
return <Suspense fallback={<PlayerStatsTableSkeleton />}>
<PlayerStatsTable />
</Suspense>;
}

View File

@@ -1,8 +1,9 @@
import TeamProfile from "@/features/teams/components/team-profile";
import { teamQueries } from "@/features/teams/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import ProfileSkeleton from "@/features/teams/components/team-profile/skeleton";
import { teamKeys, teamQueries } from "@/features/teams/queries";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { redirect, createFileRoute } from "@tanstack/react-router";
import { createFileRoute } from "@tanstack/react-router";
import { Suspense } from "react";
import { z } from "zod";
const searchSchema = z.object({
@@ -20,10 +21,13 @@ export const Route = createFileRoute("/_authed/teams/$teamId")({
collapsed: true,
withBackButton: true,
},
refresh: [teamQueries.details(params.teamId).queryKey],
refresh: [teamKeys.details(params.teamId), teamKeys.matches(params.teamId), teamKeys.stats(params.teamId)],
withPadding: false
}),
component: () => {
const { teamId } = Route.useParams();
return <TeamProfile id={teamId} />;
return <Suspense fallback={<ProfileSkeleton />}>
<TeamProfile id={teamId} />
</Suspense>;
},
});

View File

@@ -0,0 +1,82 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import {
tournamentQueries,
useTournament,
} from "@/features/tournaments/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import SeedTournament from "@/features/tournaments/components/seed-tournament";
import { Container } from "@mantine/core";
import { useMemo } from "react";
import { BracketData } from "@/features/bracket/types";
import { Match } from "@/features/matches/types";
import BracketView from "@/features/bracket/components/bracket-view";
import { SpotifyControlsBar } from "@/features/spotify/components";
export const Route = createFileRoute("/_authed/tournaments/$id/bracket")({
beforeLoad: async ({ context, params }) => {
const { queryClient } = context;
const tournament = await ensureServerQueryData(
queryClient,
tournamentQueries.details(params.id)
);
if (!tournament) throw redirect({ to: "/admin/tournaments" });
return {
tournament,
};
},
loader: ({ context }) => ({
fullWidth: true,
withPadding: false,
showSpotifyPanel: true,
header: {
withBackButton: true,
title: `${context.tournament.name}`,
},
}),
component: RouteComponent,
});
function RouteComponent() {
const { id } = Route.useParams();
const { data: tournament } = useTournament(id);
const bracket: BracketData = useMemo(() => {
if (!tournament.matches || tournament.matches.length === 0) {
return { winners: [], losers: [] };
}
const winnersMap = new Map<number, Match[]>();
const losersMap = new Map<number, Match[]>();
tournament.matches
.sort((a, b) => a.lid - b.lid)
.forEach((match) => {
if (!match.is_losers_bracket) {
if (!winnersMap.has(match.round)) {
winnersMap.set(match.round, []);
}
winnersMap.get(match.round)!.push(match);
} else {
if (!losersMap.has(match.round)) {
losersMap.set(match.round, []);
}
losersMap.get(match.round)!.push(match);
}
});
const winners = Array.from(winnersMap.entries())
.sort(([a], [b]) => a - b)
.map(([, matches]) => matches);
const losers = Array.from(losersMap.entries())
.sort(([a], [b]) => a - b)
.map(([, matches]) => matches);
return { winners, losers };
}, [tournament.matches]);
return (
<Container size="md" px={0}>
<BracketView bracket={bracket} />
</Container>
);
}

View File

@@ -3,6 +3,8 @@ import { tournamentQueries } from '@/features/tournaments/queries';
import Profile from '@/features/tournaments/components/profile';
import { z } from "zod";
import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch';
import { Suspense } from 'react';
import ProfileSkeleton from '@/features/tournaments/components/profile/skeleton';
const searchSchema = z.object({
tab: z.string().optional(),
@@ -10,9 +12,9 @@ const searchSchema = z.object({
export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
validateSearch: searchSchema,
beforeLoad: async ({ context, params }) => {
beforeLoad: ({ context, params }) => {
const { queryClient } = context;
await prefetchServerQuery(queryClient, tournamentQueries.details(params.tournamentId))
prefetchServerQuery(queryClient, tournamentQueries.details(params.tournamentId))
},
loader: ({ params, context }) => ({
header: {
@@ -20,7 +22,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
withBackButton: true,
settingsLink: context.auth.roles.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined
},
refresh: tournamentQueries.details(params.tournamentId).queryKey,
refresh: [tournamentQueries.details(params.tournamentId).queryKey],
withPadding: false
}),
component: RouteComponent,
@@ -28,5 +30,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
function RouteComponent() {
const tournamentId = Route.useParams().tournamentId;
return <Profile id={tournamentId} />
return <Suspense fallback={<ProfileSkeleton />}>
<Profile id={tournamentId} />
</Suspense>
}

View File

@@ -1,20 +1,14 @@
import Page from '@/components/page'
import { Stack } from '@mantine/core'
import { createFileRoute } from '@tanstack/react-router'
import { TournamentCard } from '@/features/tournaments/components/tournament-card'
import { tournamentQueries, useTournaments } from '@/features/tournaments/queries'
import { useAuth } from '@/contexts/auth-context'
import { useSheet } from '@/hooks/use-sheet'
import Sheet from '@/components/sheet/sheet'
import TournamentForm from '@/features/tournaments/components/tournament-form'
import { PlusIcon } from '@phosphor-icons/react'
import Button from '@/components/button'
import { tournamentQueries } from '@/features/tournaments/queries'
import { prefetchServerQuery } from '@/lib/tanstack-query/utils/prefetch'
import { Suspense } from 'react'
import TournamentCardList from '@/features/tournaments/components/tournament-card-list'
import { Skeleton, Stack } from '@mantine/core'
export const Route = createFileRoute('/_authed/tournaments/')({
beforeLoad: async ({ context }) => {
const { queryClient } = context;
await prefetchServerQuery(queryClient, tournamentQueries.list())
prefetchServerQuery(queryClient, tournamentQueries.list())
},
loader: () => ({
header: {
@@ -27,27 +21,11 @@ export const Route = createFileRoute('/_authed/tournaments/')({
})
function RouteComponent() {
const { data: tournaments } = useTournaments();
const { roles } = useAuth();
const sheet = useSheet();
return <Page>
<Stack>
{
roles?.includes("Admin") ? (
<>
<Button leftSection={<PlusIcon />} variant='subtle' onClick={sheet.open}>Create Tournament</Button>
<Sheet {...sheet.props} title='Create Tournament'>
<TournamentForm close={sheet.close} />
</Sheet>
</>
) : null
}
{
tournaments?.map((tournament: any) => (
<TournamentCard key={tournament.id} tournament={tournament} />
))
}
</Stack>
</Page>
return <Suspense fallback={<Stack gap="md">
{Array(10).fill(null).map((_, index) => (
<Skeleton height="120px" w="100%" />
))}
</Stack>}>
<TournamentCardList />
</Suspense>
}

View File

@@ -1,5 +1,5 @@
// API file that handles all supertokens auth routes
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute } from '@tanstack/react-router';
import { handleAuthAPIRequest } from 'supertokens-node/custom'
import { ensureSuperTokensBackend } from '@/lib/supertokens/server'
@@ -9,15 +9,19 @@ ensureSuperTokensBackend();
const superTokensHandler = handleAuthAPIRequest();
const handleRequest = async ({ request }: {request: Request}) => {
console.log("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
console.log("Handling auth request:", request);
console.log("Handling auth request:", request.method, request.url);
return superTokensHandler(request);
};
export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
GET: handleRequest,
POST: handleRequest,
PUT: handleRequest,
DELETE: handleRequest,
PATCH: handleRequest,
OPTIONS: handleRequest,
HEAD: handleRequest,
export const Route = createFileRoute('/api/auth/$')({
server: {
handlers: {
GET: handleRequest,
POST: handleRequest,
PUT: handleRequest,
DELETE: handleRequest,
PATCH: handleRequest,
OPTIONS: handleRequest,
HEAD: handleRequest,
}
}
})

View File

@@ -1,67 +1,61 @@
import { createServerFileRoute } from "@tanstack/react-start/server";
import { createFileRoute } from "@tanstack/react-router";
import { serverEvents, type ServerEvent } from "@/lib/events/emitter";
import { logger } from "@/lib/logger";
import { superTokensRequestMiddleware } from "@/utils/supertokens";
export const ServerRoute = createServerFileRoute("/api/events/$").middleware([superTokensRequestMiddleware]).methods({
GET: ({ request, context }) => {
logger.info('ServerEvents | New connection', context?.userAuthId);
const stream = new ReadableStream({
start(controller) {
// Send initial connection messages
const connectMessage = `data: ${JSON.stringify({ type: "connected" })}\n\n`;
controller.enqueue(new TextEncoder().encode(connectMessage));
export const Route = createFileRoute("/api/events/$")({
server: {
middleware: [superTokensRequestMiddleware],
handlers: {
GET: ({ request, context }) => {
const stream = new ReadableStream({
start(controller) {
const connectMessage = `data: ${JSON.stringify({ type: "connected" })}\n\n`;
controller.enqueue(new TextEncoder().encode(connectMessage));
// Listen for events and broadcast to all connections
const handleEvent = (event: ServerEvent) => {
logger.info('ServerEvents | Event received', event);
const message = `data: ${JSON.stringify(event)}\n\n`;
try {
controller.enqueue(new TextEncoder().encode(message));
} catch (error) {
logger.error("ServerEvents | Error sending SSE message", error);
}
};
const handleEvent = (event: ServerEvent) => {
logger.info("ServerEvents | Event received", event);
const message = `data: ${JSON.stringify(event)}\n\n`;
try {
controller.enqueue(new TextEncoder().encode(message));
} catch (error) {
logger.error("ServerEvents | Error sending SSE message", error);
}
};
serverEvents.on("test", handleEvent);
serverEvents.on("test", handleEvent);
serverEvents.on("match", handleEvent);
serverEvents.on("reaction", handleEvent);
// Keep alive ping every 30 seconds
const pingInterval = setInterval(() => {
try {
const pingMessage = `data: ${JSON.stringify({ type: "ping" })}\n\n`;
controller.enqueue(new TextEncoder().encode(pingMessage));
} catch (e) {
clearInterval(pingInterval);
controller.close();
}
}, 30000);
const pingInterval = setInterval(() => {
try {
const pingMessage = `data: ${JSON.stringify({ type: "ping" })}\n\n`;
controller.enqueue(new TextEncoder().encode(pingMessage));
} catch (e) {
clearInterval(pingInterval);
}
}, 30000);
const cleanup = () => {
serverEvents.off("test", handleEvent);
clearInterval(pingInterval);
try {
logger.info('ServerEvents | Closing connection', context?.userAuthId);
controller.close();
} catch (e) {
logger.error('ServerEvents | Error closing controller', e);
}
};
const cleanup = () => {
serverEvents.off("test", handleEvent);
clearInterval(pingInterval);
};
request.signal?.addEventListener("abort", cleanup);
request.signal?.addEventListener("abort", cleanup);
return cleanup;
},
});
return cleanup;
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Cache-Control",
},
});
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Cache-Control",
},
});
},
},
});
});

View File

@@ -1,95 +1,113 @@
import { createServerFileRoute } from "@tanstack/react-start/server";
import { createFileRoute } from "@tanstack/react-router";
import { logger } from "@/lib/logger";
export const ServerRoute = createServerFileRoute("/api/files/$collection/$recordId/$file").methods({
GET: async ({ params, request }) => {
try {
const { collection, recordId, file } = params;
const pocketbaseUrl = process.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090';
const fileUrl = `${pocketbaseUrl}/api/files/${collection}/${recordId}/${file}`;
logger.info('File proxy', {
collection,
recordId,
file,
targetUrl: fileUrl
});
export const Route = createFileRoute(
"/api/files/$collection/$recordId/$file"
)({
server: {
handlers: {
GET: async ({ params, request }) => {
try {
const { collection, recordId, file } = params;
const pocketbaseUrl =
process.env.POCKETBASE_URL || "http://127.0.0.1:8090";
const fileUrl = `${pocketbaseUrl}/api/files/${collection}/${recordId}/${file}`;
const response = await fetch(fileUrl, {
method: 'GET',
headers: {
...(request.headers.get('range') && { 'Range': request.headers.get('range')! }),
...(request.headers.get('if-none-match') && { 'If-None-Match': request.headers.get('if-none-match')! }),
...(request.headers.get('if-modified-since') && { 'If-Modified-Since': request.headers.get('if-modified-since')! }),
},
});
logger.info("File proxy", {
collection,
recordId,
file,
targetUrl: fileUrl,
});
if (!response.ok) {
logger.error('PocketBase file request failed', {
status: response.status,
statusText: response.statusText,
url: fileUrl
});
const response = await fetch(fileUrl, {
method: "GET",
headers: {
...(request.headers.get("range") && {
Range: request.headers.get("range")!,
}),
...(request.headers.get("if-none-match") && {
"If-None-Match": request.headers.get("if-none-match")!,
}),
...(request.headers.get("if-modified-since") && {
"If-Modified-Since": request.headers.get("if-modified-since")!,
}),
},
});
if (response.status === 404) {
return new Response('File not found', { status: 404 });
if (!response.ok) {
logger.error("PocketBase file request failed", {
status: response.status,
statusText: response.statusText,
url: fileUrl,
});
if (response.status === 404) {
return new Response("File not found", { status: 404 });
}
return new Response(`PocketBase error: ${response.statusText}`, {
status: response.status,
});
}
const body = response.body;
const responseHeaders = new Headers();
const headers = [
"content-type",
"content-length",
"content-disposition",
"etag",
"last-modified",
"cache-control",
"accept-ranges",
"content-range",
];
headers.forEach((header) => {
const value = response.headers.get(header);
if (value) {
responseHeaders.set(header, value);
}
});
responseHeaders.set("Access-Control-Allow-Origin", "*");
responseHeaders.set(
"Access-Control-Allow-Methods",
"GET, HEAD, OPTIONS"
);
responseHeaders.set(
"Access-Control-Allow-Headers",
"Range, If-None-Match, If-Modified-Since"
);
logger.info("File proxy response", {
status: response.status,
contentType: response.headers.get("content-type"),
contentLength: response.headers.get("content-length"),
});
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
} catch (error) {
logger.error("File proxy error", error);
return new Response("Internal server error", { status: 500 });
}
},
return new Response(`PocketBase error: ${response.statusText}`, {
status: response.status
OPTIONS: () => {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Max-Age": "86400",
},
});
}
const body = response.body;
const responseHeaders = new Headers();
const headers = [
'content-type',
'content-length',
'content-disposition',
'etag',
'last-modified',
'cache-control',
'accept-ranges',
'content-range'
];
headers.forEach(header => {
const value = response.headers.get(header);
if (value) {
responseHeaders.set(header, value);
}
});
responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
responseHeaders.set('Access-Control-Allow-Headers', 'Range, If-None-Match, If-Modified-Since');
logger.info('File proxy response', {
status: response.status,
contentType: response.headers.get('content-type'),
contentLength: response.headers.get('content-length')
});
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders
});
} catch (error) {
logger.error('File proxy error', error);
return new Response('Internal server error', { status: 500 });
}
},
},
},
OPTIONS: () => {
return new Response(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Max-Age': '86400',
}
});
}
});
});

View File

@@ -0,0 +1,145 @@
import { createFileRoute } from "@tanstack/react-router";
const SPOTIFY_CLIENT_ID = process.env.VITE_SPOTIFY_CLIENT_ID!;
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!;
const SPOTIFY_REDIRECT_URI = process.env.VITE_SPOTIFY_REDIRECT_URI!;
export const Route = createFileRoute("/api/spotify/callback")({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
const getReturnPath = (state: string | null): string => {
if (!state) return "/";
try {
const decodedState = JSON.parse(atob(state));
return decodedState.returnPath || "/";
} catch {
return "/";
}
};
try {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const error = url.searchParams.get("error");
const returnPath = getReturnPath(state);
if (error) {
console.error("Spotify OAuth error:", error);
return new Response(null, {
status: 302,
headers: {
Location:
returnPath + "?spotify_error=" + encodeURIComponent(error),
},
});
}
if (!code || !state) {
console.error("Missing code or state:", {
code: !!code,
state: !!state,
});
return new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_error=missing_code_or_state",
},
});
}
console.log("Token exchange attempt:", {
client_id: SPOTIFY_CLIENT_ID,
redirect_uri: SPOTIFY_REDIRECT_URI,
has_code: !!code,
has_state: !!state,
});
const tokenResponse = await fetch(
"https://accounts.spotify.com/api/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString("base64")}`,
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: SPOTIFY_REDIRECT_URI,
}),
}
);
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error("Token exchange error:", {
status: tokenResponse.status,
statusText: tokenResponse.statusText,
body: errorText,
redirect_uri: SPOTIFY_REDIRECT_URI,
});
const errorParam = encodeURIComponent(
`${tokenResponse.status}: ${errorText}`
);
return new Response(null, {
status: 302,
headers: {
Location: `${returnPath}?spotify_error=token_exchange_failed&details=${errorParam}`,
},
});
}
const tokens = await tokenResponse.json();
console.log("Token exchange successful:", {
has_access_token: !!tokens.access_token,
has_refresh_token: !!tokens.refresh_token,
expires_in: tokens.expires_in,
});
console.log("Decoded return path:", returnPath);
const response = new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_auth=success",
},
});
const isSecure = import.meta.env.NODE_ENV === "production";
const cookieOptions = `HttpOnly; ${isSecure ? 'Secure; ' : ''}SameSite=Lax; Path=/; Max-Age=${tokens.expires_in}`;
response.headers.append(
"Set-Cookie",
`spotify_access_token=${tokens.access_token}; ${cookieOptions}`
);
if (tokens.refresh_token) {
const refreshCookieOptions = `HttpOnly; ${isSecure ? 'Secure; ' : ''}SameSite=Lax; Path=/; Max-Age=${60 * 60 * 24 * 30}`; // 30 days
response.headers.append(
"Set-Cookie",
`spotify_refresh_token=${tokens.refresh_token}; ${refreshCookieOptions}`
);
}
return response;
} catch (error) {
console.error("Spotify callback error:", error);
const url = new URL(request.url);
const state = url.searchParams.get("state");
const returnPath = getReturnPath(state);
return new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_error=callback_failed",
},
});
}
},
},
},
});

View File

@@ -0,0 +1,60 @@
import { createFileRoute } from "@tanstack/react-router";
import { SpotifyWebApiClient } from "@/lib/spotify/client";
import type { SpotifyPlaybackSnapshot } from "@/lib/spotify/types";
export const Route = createFileRoute("/api/spotify/capture")({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => {
try {
const cookies = request.headers.get("Cookie") || "";
const accessTokenMatch = cookies.match(
/spotify_access_token=([^;]+)/
);
if (!accessTokenMatch) {
return new Response(
JSON.stringify({ error: "No access token found" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
const accessToken = decodeURIComponent(accessTokenMatch[1]);
const spotifyClient = new SpotifyWebApiClient(accessToken);
const snapshot = await spotifyClient.createPlaybackSnapshot();
if (!snapshot) {
return new Response(
JSON.stringify({ error: "No active playback to capture" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
return new Response(JSON.stringify({ snapshot }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Spotify capture error:", error);
const errorMessage =
error instanceof Error
? error.message
: "Failed to capture playback state";
return new Response(JSON.stringify({ error: errorMessage }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
},
},
},
});

View File

@@ -0,0 +1,203 @@
import { createFileRoute } from "@tanstack/react-router";
import { SpotifyWebApiClient } from "@/lib/spotify/client";
function getAccessTokenFromCookies(request: Request): string | null {
const cookieHeader = request.headers.get("cookie");
if (!cookieHeader) return null;
const cookies = Object.fromEntries(
cookieHeader.split("; ").map((c) => c.split("="))
);
return cookies.spotify_access_token || null;
}
export const Route = createFileRoute("/api/spotify/playback")({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => {
try {
const accessToken = getAccessTokenFromCookies(request);
if (!accessToken) {
return new Response(
JSON.stringify({ error: "No access token found" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
const body = await request.json();
const { action, deviceId, volumePercent, trackId, positionMs } = body;
const spotifyClient = new SpotifyWebApiClient(accessToken);
switch (action) {
case "play":
await spotifyClient.play(deviceId);
break;
case "playTrack":
if (!trackId) {
return new Response(
JSON.stringify({
error: "trackId is required for playTrack action",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
await spotifyClient.playTrack(trackId, deviceId, positionMs);
break;
case "pause":
await spotifyClient.pause();
break;
case "next":
await spotifyClient.skipToNext();
break;
case "previous":
await spotifyClient.skipToPrevious();
break;
case "volume":
if (typeof volumePercent !== "number") {
return new Response(
JSON.stringify({ error: "volumePercent must be a number" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
await spotifyClient.setVolume(volumePercent);
break;
case "transfer":
if (!deviceId) {
return new Response(
JSON.stringify({
error: "deviceId is required for transfer action",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
await spotifyClient.transferPlayback(deviceId);
break;
default:
return new Response(JSON.stringify({ error: "Invalid action" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Playback control error:", error);
if (error instanceof Error) {
if (error.message.includes("NO_ACTIVE_DEVICE")) {
return new Response(
JSON.stringify({
error:
"No active device found. Please select a device first.",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
if (error.message.includes("PREMIUM_REQUIRED")) {
return new Response(
JSON.stringify({
error: "Spotify Premium is required for playback control.",
}),
{
status: 403,
headers: { "Content-Type": "application/json" },
}
);
}
console.error("Full error details:", {
message: error.message,
stack: error.stack,
name: error.name,
});
}
return new Response(
JSON.stringify({
error: "Playback control failed",
details: error instanceof Error ? error.message : "Unknown error",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
GET: async ({ request }: { request: Request }) => {
try {
const accessToken = getAccessTokenFromCookies(request);
if (!accessToken) {
return new Response(
JSON.stringify({ error: "No access token found" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
const url = new URL(request.url);
const type = url.searchParams.get("type");
const spotifyClient = new SpotifyWebApiClient(accessToken);
if (type === "devices") {
const devices = await spotifyClient.getDevices();
return new Response(JSON.stringify({ devices }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} else if (type === "state") {
const playbackState = await spotifyClient.getPlaybackState();
return new Response(JSON.stringify({ playbackState }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} else {
const [devices, playbackState] = await Promise.all([
spotifyClient.getDevices(),
spotifyClient.getPlaybackState(),
]);
return new Response(JSON.stringify({ devices, playbackState }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
} catch (error) {
console.error("Get playback data error:", error);
return new Response(
JSON.stringify({ error: "Failed to get playback data" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
},
},
});

View File

@@ -0,0 +1,71 @@
import { createFileRoute } from "@tanstack/react-router";
import { SpotifyWebApiClient } from "@/lib/spotify/client";
import type { SpotifyPlaybackSnapshot } from "@/lib/spotify/types";
export const Route = createFileRoute("/api/spotify/resume")({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => {
try {
const cookies = request.headers.get("Cookie") || "";
const accessTokenMatch = cookies.match(
/spotify_access_token=([^;]+)/
);
if (!accessTokenMatch) {
return new Response(
JSON.stringify({ error: "No access token found" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
const accessToken = decodeURIComponent(accessTokenMatch[1]);
const spotifyClient = new SpotifyWebApiClient(accessToken);
const body = await request.json();
const { snapshot } = body as { snapshot: SpotifyPlaybackSnapshot };
if (!snapshot) {
return new Response(
JSON.stringify({ error: "No snapshot provided" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
await spotifyClient.restorePlaybackSnapshot(snapshot);
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Spotify resume error:", error);
let errorMessage = "Failed to resume playback state";
if (error instanceof Error) {
if (
error.message.includes("Premium") ||
error.message.includes("403")
) {
errorMessage = "Spotify premium required";
} else {
errorMessage = error.message;
}
}
return new Response(JSON.stringify({ error: errorMessage }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
},
},
},
});

View File

@@ -0,0 +1,87 @@
import { createFileRoute } from "@tanstack/react-router";
async function getClientCredentialsToken(): Promise<string> {
const clientId = process.env.VITE_SPOTIFY_CLIENT_ID;
const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new Error("Missing Spotify client credentials");
}
const response = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
},
body: "grant_type=client_credentials",
});
if (!response.ok) {
throw new Error("Failed to get Spotify access token");
}
const data = await response.json();
return data.access_token;
}
export const Route = createFileRoute("/api/spotify/search")({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
try {
const url = new URL(request.url);
const query = url.searchParams.get("q");
if (!query) {
return new Response(
JSON.stringify({ error: "Query parameter q is required" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
// Get client credentials access token
const accessToken = await getClientCredentialsToken();
// Search using Spotify API directly
const searchUrl = `https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=20`;
const searchResponse = await fetch(searchUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!searchResponse.ok) {
throw new Error("Spotify search request failed");
}
const searchResult = await searchResponse.json();
return new Response(
JSON.stringify({ tracks: searchResult.tracks.items }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error) {
console.error("Search error:", error);
return new Response(
JSON.stringify({
error: "Search failed",
details: error instanceof Error ? error.message : "Unknown error",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
},
},
});

View File

@@ -0,0 +1,131 @@
import { createFileRoute } from "@tanstack/react-router";
const SPOTIFY_CLIENT_ID = process.env.VITE_SPOTIFY_CLIENT_ID!;
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!;
export const Route = createFileRoute("/api/spotify/token")({
server: {
handlers: {
POST: async ({ request }: { request: Request }) => {
try {
const body = await request.json();
const { refresh_token } = body;
if (!refresh_token) {
return new Response(
JSON.stringify({ error: "refresh_token is required" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const tokenResponse = await fetch(
"https://accounts.spotify.com/api/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString("base64")}`,
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token,
}),
}
);
if (!tokenResponse.ok) {
const error = await tokenResponse.json();
console.error("Token refresh error:", error);
return new Response(
JSON.stringify({
error: "Failed to refresh token",
details: error,
}),
{
status: tokenResponse.status,
headers: { "Content-Type": "application/json" },
}
);
}
const tokens = await tokenResponse.json();
return new Response(
JSON.stringify({
access_token: tokens.access_token,
expires_in: tokens.expires_in,
scope: tokens.scope,
token_type: tokens.token_type,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error) {
console.error("Token refresh endpoint error:", error);
return new Response(
JSON.stringify({ error: "Internal server error" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
GET: async ({ request }: { request: Request }) => {
try {
const cookieHeader = request.headers.get("cookie");
if (!cookieHeader) {
return new Response(JSON.stringify({ error: "No cookies found" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
const cookies = Object.fromEntries(
cookieHeader.split("; ").map((c: string) => c.split("="))
);
const accessToken = cookies.spotify_access_token;
const refreshToken = cookies.spotify_refresh_token;
if (!accessToken && !refreshToken) {
return new Response(
JSON.stringify({ error: "No Spotify tokens found" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
return new Response(
JSON.stringify({
access_token: accessToken || null,
refresh_token: refreshToken || null,
has_tokens: !!(accessToken || refreshToken),
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error) {
console.error("Get tokens endpoint error:", error);
return new Response(
JSON.stringify({ error: "Internal server error" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
},
},
});

View File

@@ -0,0 +1,148 @@
import { createFileRoute } from "@tanstack/react-router";
import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
import { z } from "zod";
const uploadSchema = z.object({
teamId: z.string().min(1, "Team ID is required"),
});
export const Route = createFileRoute("/api/teams/upload-logo")({
server: {
middleware: [superTokensRequestMiddleware],
handlers: {
POST: async ({ request, context }) => {
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
if (!userId) return new Response("Unauthenticated", { status: 401 });
const formData = await request.formData();
const teamId = formData.get("teamId") as string;
const logoFile = formData.get("logo") as File;
const validationResult = uploadSchema.safeParse({ teamId });
if (!validationResult.success) {
return new Response(
JSON.stringify({
error: "Invalid input",
details: validationResult.error.issues,
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
if (!logoFile || logoFile.size === 0) {
return new Response(
JSON.stringify({
error: "Logo file is required",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const allowedTypes = [
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
];
if (!allowedTypes.includes(logoFile.type)) {
return new Response(
JSON.stringify({
error: "Invalid file type. Only JPEG, PNG and GIF are allowed.",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) {
return new Response(
JSON.stringify({
error: "File too large. Maximum size is 10MB.",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const team = await pbAdmin.getTeam(teamId);
if (!team) {
return new Response(
JSON.stringify({
error: "Team not found",
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
}
);
}
const user = await pbAdmin.getPlayerByAuthId(userId);
if (!team.players.map((p) => p.id).includes(user?.id!) && !isAdmin)
return new Response("Unauthorized", { status: 403 });
logger.info("Uploading team logo", {
teamId,
fileName: logoFile.name,
fileSize: logoFile.size,
userId,
});
const pbFormData = new FormData();
pbFormData.append("logo", logoFile);
const updatedTeam = await pbAdmin.updateTeam(
teamId,
pbFormData as any
);
logger.info("Team logo uploaded successfully", {
teamId,
logo: updatedTeam.logo,
});
return new Response(
JSON.stringify({
success: true,
team: updatedTeam,
message: "Logo uploaded successfully",
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error: any) {
logger.error("Error uploading team logo:", error);
return new Response(
JSON.stringify({
error: "Failed to upload logo",
message: error.message || "Unknown error occurred",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
},
},
},
});

View File

@@ -1,115 +1,145 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { superTokensRequestMiddleware } from '@/utils/supertokens';
import { pbAdmin } from '@/lib/pocketbase/client';
import { logger } from '@/lib/logger';
import { z } from 'zod';
import { createFileRoute } from "@tanstack/react-router";
import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
import { z } from "zod";
const uploadSchema = z.object({
tournamentId: z.string().min(1, 'Tournament ID is required'),
tournamentId: z.string().min(1, "Tournament ID is required"),
});
export const ServerRoute = createServerFileRoute('/api/tournaments/upload-logo')
.middleware([superTokensRequestMiddleware])
.methods({
POST: async ({ request, context }) => {
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
export const Route = createFileRoute("/api/tournaments/upload-logo")({
server: {
middleware: [superTokensRequestMiddleware],
handlers: {
POST: async ({ request, context }) => {
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
if (!userId) return new Response('Unauthenticated', { status: 401 });
if (!isAdmin) return new Response('Unauthorized', { status: 403 });
if (!userId) return new Response("Unauthenticated", { status: 401 });
if (!isAdmin) return new Response("Unauthorized", { status: 403 });
const formData = await request.formData();
const tournamentId = formData.get('tournamentId') as string;
const logoFile = formData.get('logo') as File;
const formData = await request.formData();
const tournamentId = formData.get("tournamentId") as string;
const logoFile = formData.get("logo") as File;
const validationResult = uploadSchema.safeParse({ tournamentId });
if (!validationResult.success) {
return new Response(JSON.stringify({
error: 'Invalid input',
details: validationResult.error.issues
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
const validationResult = uploadSchema.safeParse({ tournamentId });
if (!validationResult.success) {
return new Response(
JSON.stringify({
error: "Invalid input",
details: validationResult.error.issues,
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
if (!logoFile || logoFile.size === 0) {
return new Response(
JSON.stringify({
error: "Logo file is required",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const allowedTypes = [
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
];
if (!allowedTypes.includes(logoFile.type)) {
return new Response(
JSON.stringify({
error: "Invalid file type. Only JPEG, PNG and GIF are allowed.",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) {
return new Response(
JSON.stringify({
error: "File too large. Maximum size is 10MB.",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
const tournament = await pbAdmin.getTournament(tournamentId);
if (!tournament) {
return new Response(
JSON.stringify({
error: "Tournament not found",
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
}
);
}
logger.info("Uploading tournament logo", {
tournamentId,
fileName: logoFile.name,
fileSize: logoFile.size,
userId,
});
}
if (!logoFile || logoFile.size === 0) {
return new Response(JSON.stringify({
error: 'Logo file is required'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
const pbFormData = new FormData();
pbFormData.append("logo", logoFile);
const updatedTournament = await pbAdmin.updateTournament(
tournamentId,
pbFormData as any
);
logger.info("Tournament logo uploaded successfully", {
tournamentId,
logo: updatedTournament.logo,
});
return new Response(
JSON.stringify({
success: true,
tournament: updatedTournament,
message: "Logo uploaded successfully",
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error: any) {
logger.error("Error uploading tournament logo:", error);
return new Response(
JSON.stringify({
error: "Failed to upload logo",
message: error.message || "Unknown error occurred",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(logoFile.type)) {
return new Response(JSON.stringify({
error: 'Invalid file type. Only JPEG, PNG and GIF are allowed.'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) {
return new Response(JSON.stringify({
error: 'File too large. Maximum size is 10MB.'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const tournament = await pbAdmin.getTournament(tournamentId);
if (!tournament) {
return new Response(JSON.stringify({
error: 'Tournament not found'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}
logger.info('Uploading tournament logo', {
tournamentId,
fileName: logoFile.name,
fileSize: logoFile.size,
userId
});
const pbFormData = new FormData();
pbFormData.append('logo', logoFile);
const updatedTournament = await pbAdmin.updateTournament(tournamentId, pbFormData as any);
logger.info('Tournament logo uploaded successfully', {
tournamentId,
logo: updatedTournament.logo
});
return new Response(JSON.stringify({
success: true,
tournament: updatedTournament,
message: 'Logo uploaded successfully'
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error: any) {
logger.error('Error uploading tournament logo:', error);
return new Response(JSON.stringify({
error: 'Failed to upload logo',
message: error.message || 'Unknown error occurred'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
});
},
},
},
});

View File

@@ -0,0 +1,44 @@
import { createFileRoute } from '@tanstack/react-router'
import { useEffect, useRef } from 'react'
import FullScreenLoader from '@/components/full-screen-loader'
import { attemptRefreshingSession } from 'supertokens-web-js/recipe/session'
import { resetRefreshFlag } from '@/lib/supertokens/client'
export const Route = createFileRoute('/refresh-session')({
component: RouteComponent,
})
function RouteComponent() {
const hasAttemptedRef = useRef(false);
useEffect(() => {
if (hasAttemptedRef.current) return;
hasAttemptedRef.current = true;
const handleRefresh = async () => {
try {
resetRefreshFlag();
const refreshed = await attemptRefreshingSession()
if (refreshed) {
const urlParams = new URLSearchParams(window.location.search)
const redirect = urlParams.get('redirect')
if (redirect && !redirect.includes('_serverFn') && !redirect.includes('/api/')) {
window.location.href = decodeURIComponent(redirect)
} else {
window.location.href = '/';
}
} else {
window.location.href = '/login'
}
} catch (error) {
window.location.href = '/login'
}
}
setTimeout(handleRefresh, 100)
}, [])
return <FullScreenLoader />
}

View File

@@ -7,23 +7,22 @@ import {
redirect,
} from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'
import {
Box,
import {
Box,
Button as MantineButton,
Text,
Title,
Stack,
Group,
Alert,
Text,
Stack,
Group,
Collapse,
Code,
ThemeIcon
Container,
Center
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useEffect } from 'react'
import toast from '@/lib/sonner'
import { logger } from '@/lib/logger'
import { ExclamationMarkIcon, XCircleIcon } from '@phosphor-icons/react'
import { XCircleIcon, WarningIcon } from '@phosphor-icons/react'
import Button from './button'
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
@@ -50,112 +49,90 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
if (errorMessage.toLowerCase().includes('unauthorized')) {
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '50vh',
padding: 'var(--mantine-spacing-xl)',
}}
>
<Stack align="center" gap="lg">
<ThemeIcon color="red" size={80} radius="xl">
<XCircleIcon size={48} />
</ThemeIcon>
<Title order={2} ta="center">Access Denied</Title>
<Text size="lg" c="dimmed" ta="center">
You don't have permission to access this.
</Text>
<Group>
<Button
variant="light"
onClick={() => window.history.back()}
>
Go Back
</Button>
<MantineButton
component={Link}
to="/"
variant="filled"
>
Home
</MantineButton>
</Group>
</Stack>
</Box>
<Container size="sm" py="xl">
<Center>
<Stack align="center" gap="md">
<XCircleIcon size={64} color="var(--mantine-color-red-6)" />
<Text size="xl" fw={600}>Access Denied</Text>
<Text c="dimmed" ta="center">
You don't have permission to access this page.
</Text>
<Group gap="sm" mt="md">
<Button
variant="light"
onClick={() => window.history.back()}
>
Go Back
</Button>
<MantineButton
component={Link}
to="/"
variant="filled"
>
Home
</MantineButton>
</Group>
</Stack>
</Center>
</Container>
)
}
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '50vh',
padding: 'var(--mantine-spacing-xl)',
}}
>
<Stack align="center" gap="lg" maw={600}>
<ThemeIcon color="red" size={80} radius="xl">
<ExclamationMarkIcon size={48} />
</ThemeIcon>
<Title order={2} ta="center">Something went wrong</Title>
<Text size="lg" c="dimmed" ta="center">
There was an unexpected error. Please try again later.
</Text>
<Container size="sm" py="xl">
<Center>
<Stack align="center" gap="md" w="100%">
<WarningIcon size={64} color="var(--mantine-color-red-6)" />
<Alert
variant="light"
color="red"
title="Error Details"
w="100%"
>
<Text mb="sm">{errorMessage}</Text>
<Button
variant="subtle"
size="compact-sm"
onClick={toggleDetails}
>
{detailsOpened ? 'Hide' : 'Show'} stack trace
</Button>
<Collapse in={detailsOpened}>
<Code block mt="md" p="md">
{errorStack}
</Code>
</Collapse>
</Alert>
<Text size="xl" fw={600}>Something went wrong</Text>
<Group>
<Button
variant="light"
onClick={() => router.invalidate()}
>
Try Again
</Button>
{isRoot ? (
<MantineButton
component={Link}
to="/"
variant="filled"
>
Home
</MantineButton>
) : (
<Text c="dimmed" ta="center">
An error occurred while loading this page.
</Text>
<Box w="100%" mt="md">
<Text size="sm" c="dimmed" mb="xs">Error: {errorMessage}</Text>
<Button
variant="filled"
onClick={() => window.history.back()}
variant="subtle"
size="compact-sm"
onClick={toggleDetails}
fullWidth
>
Go Back
{detailsOpened ? 'Hide' : 'Show'} details
</Button>
)}
</Group>
</Stack>
</Box>
<Collapse in={detailsOpened}>
<Code block mt="sm" p="sm" style={{ fontSize: '11px' }}>
{errorStack}
</Code>
</Collapse>
</Box>
<Group gap="sm" mt="lg">
<Button
variant="light"
onClick={() => router.invalidate()}
>
Retry
</Button>
{isRoot ? (
<MantineButton
component={Link}
to="/"
variant="filled"
>
Home
</MantineButton>
) : (
<Button
variant="filled"
onClick={() => window.history.back()}
>
Go Back
</Button>
)}
</Group>
</Stack>
</Center>
</Container>
)
}

View File

@@ -2,7 +2,14 @@ import {
Avatar as MantineAvatar,
AvatarProps as MantineAvatarProps,
Paper,
Modal,
Image,
Group,
Text,
ActionIcon,
} from "@mantine/core";
import { useState } from "react";
import { XIcon } from "@phosphor-icons/react";
interface AvatarProps
extends Omit<MantineAvatarProps, "radius" | "color" | "size"> {
@@ -10,6 +17,8 @@ interface AvatarProps
size?: number;
radius?: string | number;
withBorder?: boolean;
disableFullscreen?: boolean;
contain?: boolean;
}
const Avatar = ({
@@ -17,26 +26,122 @@ const Avatar = ({
size = 35,
radius = "100%",
withBorder = true,
disableFullscreen = false,
contain = false,
...props
}: AvatarProps) => {
const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
const hasImage = Boolean(props.src);
const handleAvatarClick = () => {
if (hasImage && !disableFullscreen) {
setIsFullscreenOpen(true);
}
};
return (
<Paper p={size / 20} radius={radius} withBorder={withBorder}>
<MantineAvatar
alt={name}
key={name}
name={name}
color="initials"
size={size}
<>
<Paper
p={size / 20}
radius={radius}
w="fit-content"
withBorder={withBorder}
style={{
cursor: hasImage && !disableFullscreen ? 'pointer' : 'default',
transition: 'transform 0.15s ease',
}}
onMouseEnter={(e) => {
if (hasImage && !disableFullscreen) {
e.currentTarget.style.transform = 'scale(1.02)';
}
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)';
}}
onClick={handleAvatarClick}
>
<MantineAvatar
alt={name}
key={name}
name={name}
color="initials"
size={size}
radius={radius}
w={size}
styles={{
image: {
objectFit: contain ? 'contain' : 'cover',
},
}}
{...props}
/>
</Paper>
<Modal
opened={isFullscreenOpen}
onClose={() => setIsFullscreenOpen(false)}
size="auto"
centered
withCloseButton={false}
overlayProps={{
backgroundOpacity: 0.9,
blur: 2,
}}
styles={{
image: {
objectFit: "contain",
content: {
background: 'transparent',
border: 'none',
},
body: {
padding: 0,
},
}}
{...props}
/>
</Paper>
>
<div style={{ position: 'relative', maxWidth: '90vw', maxHeight: '90vh' }}>
<ActionIcon
variant="filled"
color="dark"
size="lg"
radius="xl"
style={{
position: 'absolute',
top: -10,
right: -10,
zIndex: 1000,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
}}
onClick={() => setIsFullscreenOpen(false)}
>
<XIcon size={18} color="white" />
</ActionIcon>
<Image
src={props.src}
alt={name}
fit="contain"
style={{
borderRadius: 8,
maxWidth: '90vw',
maxHeight: '90vh',
}}
/>
<Group
justify="center"
mt="md"
style={{
position: 'absolute',
bottom: -50,
left: '50%',
transform: 'translateX(-50%)',
}}
>
<Text c="white" size="sm" fw={500}>
{name}
</Text>
</Group>
</div>
</Modal>
</>
);
};

View File

@@ -0,0 +1,57 @@
import useNow from '@/hooks/use-now';
import { Text, Group } from '@mantine/core';
import { useMemo } from 'react';
interface CountdownProps {
date: Date;
label?: string;
color?: string;
}
interface TimeLeft {
days: number;
hours: number;
minutes: number;
seconds: number;
}
function calculateTimeLeft(targetDate: Date, currentTime = new Date()): TimeLeft {
const difference = targetDate.getTime() - currentTime.getTime();
if (difference <= 0) {
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
}
return {
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60)
};
}
export function Countdown({ date, label, color }: CountdownProps) {
const now = useNow();
const timeLeft = useMemo(() => calculateTimeLeft(date, now), [date, now]);
const formatTime = () => {
const pad = (num: number) => num.toString().padStart(2, '0');
if (timeLeft.days > 0) {
return `${timeLeft.days}d ${pad(timeLeft.hours)}:${pad(timeLeft.minutes)}:${pad(timeLeft.seconds)}`;
} else {
return `${pad(timeLeft.hours)}:${pad(timeLeft.minutes)}:${pad(timeLeft.seconds)}`;
}
};
return (
<Group gap="xs">
{label && <Text size='sm' fw={500}>{label}:</Text>}
<Text size='sm' fw={600} c={color} ff="monospace">
{formatTime()}
</Text>
</Group>
);
}
export default Countdown;

View File

@@ -0,0 +1,13 @@
import { Center, Container, Loader, Stack } from "@mantine/core";
const FullScreenLoader = () => (
<Container h="100dvh" w="100dvw">
<Center h="100%">
<Stack align="center" gap="md">
<Loader size="xl" />
</Stack>
</Center>
</Container>
);
export default FullScreenLoader;

View File

@@ -6,14 +6,16 @@ interface ListLinkProps {
label: string;
to: string;
Icon?: Icon;
disabled?: boolean
}
const ListLink = ({ label, to, Icon }: ListLinkProps) => {
const ListLink = ({ label, to, Icon, disabled=false }: ListLinkProps) => {
const navigate = useNavigate();
return (
<>
<NavLink
disabled={disabled}
w="100%"
p="md"
component={"button"}

View File

@@ -1,5 +1,7 @@
import { Container, ContainerProps } from "@mantine/core";
import { Container, ContainerProps, Box } from "@mantine/core";
import useRouterConfig from "@/features/core/hooks/use-router-config";
import BackButton from "@/features/core/components/back-button";
import SettingsButton from "@/features/core/components/settings-button";
interface PageProps extends ContainerProps, React.PropsWithChildren {
noPadding?: boolean;
@@ -16,8 +18,15 @@ const Page = ({ children, noPadding, fullWidth, ...props }: PageProps) => {
m={0}
maw={fullWidth ? '100%' : 600}
mx="auto"
pos="relative"
{...props}
>
{header.collapsed && header.withBackButton && (
<BackButton top={4} />
)}
{header.collapsed && header.settingsLink && (
<SettingsButton to={header.settingsLink} />
)}
{children}
</Container>
);

View File

@@ -0,0 +1,41 @@
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor as MantineRichTextEditor } from '@mantine/tiptap';
interface RichTextEditorProps {
value: string;
onChange: (value: string) => void;
}
export function RichTextEditor({
value,
onChange,
}: RichTextEditorProps) {
const editor = useEditor({
extensions: [StarterKit],
content: value,
immediatelyRender: false,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
});
return (
<MantineRichTextEditor editor={editor}>
<MantineRichTextEditor.Toolbar>
<MantineRichTextEditor.ControlsGroup>
<MantineRichTextEditor.Bold />
<MantineRichTextEditor.Italic />
</MantineRichTextEditor.ControlsGroup>
<MantineRichTextEditor.ControlsGroup>
<MantineRichTextEditor.Blockquote />
<MantineRichTextEditor.Hr />
<MantineRichTextEditor.BulletList />
<MantineRichTextEditor.OrderedList />
</MantineRichTextEditor.ControlsGroup>
</MantineRichTextEditor.Toolbar>
<MantineRichTextEditor.Content h="45vh" />
</MantineRichTextEditor>
);
}

View File

@@ -1,7 +1,6 @@
import { Box, Container, useComputedColorScheme } from "@mantine/core";
import { PropsWithChildren, useEffect } from "react";
import { Box, Container, Flex, Loader, useComputedColorScheme } from "@mantine/core";
import { PropsWithChildren, Suspense, useEffect, useRef } from "react";
import { Drawer as VaulDrawer } from "vaul";
import { useMantineColorScheme } from "@mantine/core";
import styles from "./styles.module.css";
interface DrawerProps extends PropsWithChildren {
@@ -17,6 +16,7 @@ const Drawer: React.FC<DrawerProps> = ({
onChange,
}) => {
const colorScheme = useComputedColorScheme("light");
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const appElement = document.querySelector(".app") as HTMLElement;
@@ -59,11 +59,56 @@ const Drawer: React.FC<DrawerProps> = ({
};
}, [opened, colorScheme]);
useEffect(() => {
if (!opened || !contentRef.current) return;
const updateDrawerHeight = () => {
if (contentRef.current) {
const drawerContent = contentRef.current;
const visualViewport = window.visualViewport;
if (visualViewport) {
const availableHeight = visualViewport.height;
const maxDrawerHeight = Math.min(availableHeight * 0.75, window.innerHeight * 0.75);
drawerContent.style.maxHeight = `${maxDrawerHeight}px`;
} else {
drawerContent.style.maxHeight = '75vh';
}
}
};
const resizeObserver = new ResizeObserver(() => {
if (contentRef.current) {
const drawerContent = contentRef.current.closest('[data-vaul-drawer-wrapper]');
if (drawerContent) {
(drawerContent as HTMLElement).style.height = 'auto';
(drawerContent as HTMLElement).offsetHeight;
}
}
});
updateDrawerHeight();
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', updateDrawerHeight);
}
resizeObserver.observe(contentRef.current);
return () => {
resizeObserver.disconnect();
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', updateDrawerHeight);
}
};
}, [opened, children]);
return (
<VaulDrawer.Root open={opened} onOpenChange={onChange}>
<VaulDrawer.Root repositionInputs={false} open={opened} onOpenChange={onChange}>
<VaulDrawer.Portal>
<VaulDrawer.Overlay className={styles.drawerOverlay} />
<VaulDrawer.Content className={styles.drawerContent}>
<VaulDrawer.Content className={styles.drawerContent} aria-describedby="drawer" ref={contentRef}>
<Container flex={1} p="md">
<Box
mb="sm"
@@ -74,9 +119,15 @@ const Drawer: React.FC<DrawerProps> = ({
mr="auto"
style={{ borderRadius: "9999px" }}
/>
<Container mah="fit-content" mx="auto" maw="28rem" px={0}>
<Container mx="auto" maw="28rem" px={0}>
<VaulDrawer.Title>{title}</VaulDrawer.Title>
{children}
<Suspense fallback={
<Flex justify='center' align='center' w='100%' h={400}>
<Loader size='lg' />
</Flex>
}>
{children}
</Suspense>
</Container>
</Container>
</VaulDrawer.Content>

View File

@@ -1,5 +1,5 @@
import { Modal as MantineModal, Title } from "@mantine/core";
import { PropsWithChildren } from "react";
import { Flex, Loader, Modal as MantineModal, Title } from "@mantine/core";
import { PropsWithChildren, Suspense } from "react";
interface ModalProps extends PropsWithChildren {
title?: string;
@@ -13,7 +13,15 @@ const Modal: React.FC<ModalProps> = ({ title, children, opened, onClose }) => (
onClose={onClose}
title={<Title order={3}>{title}</Title>}
>
{children}
<Suspense
fallback={
<Flex justify="center" align="center" w="100%" h={400}>
<Loader size="lg" />
</Flex>
}
>
{children}
</Suspense>
</MantineModal>
);

View File

@@ -2,7 +2,7 @@ import { PropsWithChildren, useCallback } from "react";
import { useIsMobile } from "@/hooks/use-is-mobile";
import Drawer from "./drawer";
import Modal from "./modal";
import { Box, ScrollArea } from "@mantine/core";
import { ScrollArea } from "@mantine/core";
interface SheetProps extends PropsWithChildren {
title?: string;
@@ -23,14 +23,14 @@ const Sheet: React.FC<SheetProps> = ({ title, children, opened, onChange }) => {
onChange={onChange}
onClose={handleClose}
>
<ScrollArea
style={{ flex: 1 }}
<ScrollArea.Autosize
style={{ flex: 1, maxHeight: '75dvh' }}
scrollbarSize={8}
scrollbars="y"
type="scroll"
>
<Box mah="70vh">{children}</Box>
</ScrollArea>
{children}
</ScrollArea.Autosize>
</SheetComponent>
);
};

View File

@@ -1,6 +1,6 @@
import { Box, Text, UnstyledButton, Flex, Stack } from "@mantine/core";
import { CaretRightIcon } from "@phosphor-icons/react";
import { ComponentType, useContext } from "react";
import React, { ComponentType, useContext } from "react";
import { SlidePanelContext } from "./slide-panel-context";
interface SlidePanelFieldProps {
@@ -11,7 +11,7 @@ interface SlidePanelFieldProps {
title: string;
label?: string;
placeholder?: string;
formatValue?: (value: any) => string;
formatValue?: (value: any) => string | React.ReactNode;
componentProps?: Record<string, any>;
withAsterisk?: boolean;
error?: string;

View File

@@ -17,6 +17,7 @@ interface SlidePanelProps {
onCancel?: () => void;
submitText?: string;
cancelText?: string;
cancelColor?: string;
maxHeight?: string;
formProps?: Record<string, any>;
loading?: boolean;
@@ -28,6 +29,7 @@ const SlidePanel = ({
onCancel,
submitText = "Submit",
cancelText = "Cancel",
cancelColor = "red",
maxHeight = "70vh",
formProps = {},
loading = false,
@@ -114,7 +116,7 @@ const SlidePanel = ({
{onCancel && (
<Button
variant="subtle"
color="red"
color={cancelColor}
fullWidth
onClick={onCancel}
type="button"
@@ -165,14 +167,11 @@ const SlidePanel = ({
bg="var(--mantine-color-dimmed)"
my="xs"
/>
<Box>
<panelConfig.Component
value={tempValue}
onChange={setTempValue}
{...(panelConfig.componentProps || {})}
/>
</Box>
<panelConfig.Component
value={tempValue}
onChange={setTempValue}
{...(panelConfig.componentProps || {})}
/>
</>
)}
</Box>

View File

@@ -11,10 +11,12 @@
border-top-left-radius: 20px;
border-top-right-radius: 20px;
margin-top: 24px;
height: fit-content;
height: auto !important;
min-height: fit-content;
position: fixed;
bottom: 0;
left: 0;
right: 0;
outline: none;
transition: height 0.2s ease-out, max-height 0.2s ease-out;
}

View File

@@ -0,0 +1,161 @@
import {
Box,
Text,
Group,
Stack,
ThemeIcon,
Skeleton,
Divider,
} from "@mantine/core";
import {
CrownIcon,
XIcon,
FireIcon,
ShieldIcon,
ChartLineUpIcon,
ShieldCheckIcon,
BoxingGloveIcon,
Icon,
ArrowUpIcon,
ArrowDownIcon,
} from "@phosphor-icons/react";
import { BaseStats } from "@/types/stats";
interface StatsOverviewProps {
statsData: BaseStats | null;
isLoading?: boolean;
}
const StatItem = ({
label,
value,
suffix = "",
Icon,
}: {
label: string;
value: number | null;
suffix?: string;
Icon?: Icon;
isLoading?: boolean;
}) => {
return (
<Group justify="space-between" align="center" py="md" px="sm">
<Group gap="sm" align="center">
{Icon && (
<ThemeIcon size="md" variant="light" radius="sm" color="gray">
<Icon size={16} />
</ThemeIcon>
)}
<Text size="sm" fw={500}>
{label}
</Text>
</Group>
{value !== null ? (
<Text size="sm" fw={700} c="dimmed">
{`${value}${suffix}`}
</Text>
) : (
<Skeleton width={20} height={20} />
)}
</Group>
);
};
const StatsOverview = ({ statsData, isLoading = false }: StatsOverviewProps) => {
if (!statsData && !isLoading) {
return (
<Box p="sm" h="auto" mih={200}>
<Text ta="center" size="sm" fw={600} c="dimmed">
No stats available yet
</Text>
</Box>
);
}
if (!statsData) return null;
const overallStats = {
matches: statsData.matches,
wins: statsData.wins,
losses: statsData.losses,
total_cups_made: statsData.total_cups_made,
total_cups_against: statsData.total_cups_against,
};
const avgCupsPerMatch =
overallStats.matches > 0
? parseFloat((overallStats.total_cups_made / overallStats.matches).toFixed(1))
: 0;
const avgCupsAgainstPerMatch =
overallStats.matches > 0
? parseFloat((overallStats.total_cups_against / overallStats.matches).toFixed(1))
: 0;
const avgMarginOfVictory = statsData.margin_of_victory ? parseFloat(statsData.margin_of_victory.toFixed(1)) : 0;
const avgMarginOfLoss = statsData.margin_of_loss ? parseFloat(statsData.margin_of_loss.toFixed(1)) : 0;
const allStats = [
{ label: "Matches Played", value: overallStats.matches, Icon: BoxingGloveIcon },
{ label: "Wins", value: overallStats.wins, Icon: CrownIcon },
{ label: "Losses", value: overallStats.losses, Icon: XIcon },
{ label: "Cups Made", value: overallStats.total_cups_made, Icon: FireIcon },
{ label: "Cups Against", value: overallStats.total_cups_against, Icon: ShieldIcon },
{ label: "Avg Cups Per Game", value: avgCupsPerMatch > 0 ? avgCupsPerMatch : null, Icon: ChartLineUpIcon },
{ label: "Avg Cups Against", value: avgCupsAgainstPerMatch > 0 ? avgCupsAgainstPerMatch : null, Icon: ShieldCheckIcon },
{ label: "Avg Win Margin", value: avgMarginOfVictory > 0 ? avgMarginOfVictory : null, Icon: ArrowUpIcon },
{ label: "Avg Loss Margin", value: avgMarginOfLoss > 0 ? avgMarginOfLoss : null, Icon: ArrowDownIcon },
];
return (
<Box>
<Stack gap={0}>
{allStats.map((stat, index) => (
<Box key={stat.label}>
<StatItem
label={stat.label}
value={stat.value}
Icon={stat.Icon}
isLoading={isLoading}
/>
{index < allStats.length - 1 && <Divider />}
</Box>
))}
</Stack>
</Box>
);
};
export const StatsSkeleton = () => {
const skeletonStats = [
{ label: "Matches Played", Icon: BoxingGloveIcon },
{ label: "Wins", Icon: CrownIcon },
{ label: "Losses", Icon: XIcon },
{ label: "Cups Made", Icon: FireIcon },
{ label: "Cups Against", Icon: ShieldIcon },
{ label: "Avg Cups Per Game", Icon: ChartLineUpIcon },
{ label: "Avg Cups Against", Icon: ShieldCheckIcon },
{ label: "Avg Win Margin", Icon: ArrowUpIcon },
{ label: "Avg Loss Margin", Icon: ArrowDownIcon },
];
return (
<Box>
<Stack gap={0}>
{skeletonStats.map((stat, index) => (
<Box key={stat.label}>
<StatItem
label={stat.label}
value={null}
Icon={stat.Icon}
isLoading={true}
/>
{index < skeletonStats.length - 1 && <Divider />}
</Box>
))}
</Stack>
</Box>
);
};
export default StatsOverview;

View File

@@ -86,7 +86,7 @@ function SwipeableTabs({
}
}, [search?.tab]);
useEffect(() => {
const updateHeight = useCallback(() => {
const activeSlideRef = slideRefs.current[activeTab];
if (activeSlideRef) {
const height = activeSlideRef.scrollHeight;
@@ -94,6 +94,33 @@ function SwipeableTabs({
}
}, [activeTab]);
useEffect(() => {
updateHeight();
}, [activeTab, updateHeight]);
useEffect(() => {
const timeoutId = setTimeout(updateHeight, 0);
return () => clearTimeout(timeoutId);
}, [updateHeight]);
useEffect(() => {
const activeSlideRef = slideRefs.current[activeTab];
if (!activeSlideRef) return;
let timeoutId: any;
const resizeObserver = new ResizeObserver(() => {
clearTimeout(timeoutId);
timeoutId = setTimeout(updateHeight, 16);
});
resizeObserver.observe(activeSlideRef);
return () => {
resizeObserver.disconnect();
clearTimeout(timeoutId);
};
}, [activeTab, updateHeight]);
const setControlRef = useCallback(
(index: number) => (node: HTMLSpanElement | null) => {
controlsRefs.current[index] = node;
@@ -109,13 +136,14 @@ function SwipeableTabs({
);
return (
<Box>
<Box style={{ touchAction: "pan-y" }}>
<Box
ref={setRootRef}
pos="sticky"
top={0}
style={{
display: "flex",
paddingInline: "var(--mantine-spacing-md)",
marginBottom: "var(--mantine-spacing-md)",
zIndex: 100,
backgroundColor: "var(--mantine-color-body)",
@@ -137,7 +165,7 @@ function SwipeableTabs({
onClick={() => changeTab(index)}
style={{
flex: 1,
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-md)",
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-xs)",
textAlign: "center",
color:
activeTab === index
@@ -155,7 +183,7 @@ function SwipeableTabs({
component="span"
style={{
display: "inline-block",
paddingInline: "1rem",
paddingInline: "0.5rem",
paddingBottom: "0.25rem",
}}
ref={setControlRef(index)}
@@ -176,6 +204,7 @@ function SwipeableTabs({
overflow: "hidden",
height: carouselHeight === "auto" ? "auto" : `${carouselHeight}px`,
transition: "height 300ms ease",
touchAction: "pan-y",
}}
>
{tabs.map((tab, index) => (

View File

@@ -0,0 +1,175 @@
import { useState, useRef, useEffect, ReactNode } from "react";
import { TextInput, Loader, Paper, Stack, Box, Text } from "@mantine/core";
import { useDebouncedCallback } from "@mantine/hooks";
export interface TypeaheadOption<T = any> {
id: string;
data: T;
}
export interface TypeaheadProps<T> {
onSelect: (option: TypeaheadOption<T>) => void;
searchFn: (query: string) => Promise<TypeaheadOption<T>[]>;
renderOption: (option: TypeaheadOption<T>, isSelected?: boolean) => ReactNode;
format?: (option: TypeaheadOption<T>) => string;
placeholder?: string;
debounceMs?: number;
disabled?: boolean;
initialValue?: string;
}
const Typeahead = <T,>({
onSelect,
searchFn,
renderOption,
format,
placeholder = "Search...",
debounceMs = 300,
disabled = false,
initialValue = ""
}: TypeaheadProps<T>) => {
const [searchQuery, setSearchQuery] = useState(initialValue);
const [searchResults, setSearchResults] = useState<TypeaheadOption<T>[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const debouncedSearch = useDebouncedCallback(async (query: string) => {
if (!query.trim()) {
setSearchResults([]);
setIsOpen(false);
return;
}
setIsLoading(true);
try {
const results = await searchFn(query);
setSearchResults(results);
setIsOpen(results.length > 0);
setSelectedIndex(-1);
} catch (error) {
console.error('Search failed:', error);
setSearchResults([]);
setIsOpen(false);
} finally {
setIsLoading(false);
}
}, debounceMs);
const handleSearchChange = (value: string) => {
setSearchQuery(value);
debouncedSearch(value);
};
const handleOptionSelect = (option: TypeaheadOption<T>) => {
onSelect(option);
const displayValue = format ? format(option) : String(option.data);
setSearchQuery(displayValue);
setIsOpen(false);
setSelectedIndex(-1);
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (!isOpen || searchResults.length === 0) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setSelectedIndex(prev => (prev < searchResults.length - 1 ? prev + 1 : prev));
break;
case 'ArrowUp':
e.preventDefault();
setSelectedIndex(prev => (prev > 0 ? prev - 1 : prev));
break;
case 'Enter':
e.preventDefault();
if (selectedIndex >= 0 && searchResults[selectedIndex]) {
handleOptionSelect(searchResults[selectedIndex]);
}
break;
case 'Escape':
setIsOpen(false);
setSelectedIndex(-1);
break;
}
};
return (
<Box ref={containerRef} pos="relative" w="100%">
<TextInput
ref={inputRef}
value={searchQuery}
onChange={(event) => handleSearchChange(event.currentTarget.value)}
onKeyDown={handleKeyDown}
onFocus={() => {
if (searchResults.length > 0) setIsOpen(true);
}}
placeholder={placeholder}
rightSection={isLoading ? <Loader size="xs" /> : null}
disabled={disabled}
/>
{isOpen && (
<Paper
shadow="md"
p={0}
bd="1px solid var(--mantine-color-dimmed)"
style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
zIndex: 9999,
maxHeight: '160px',
overflowY: 'auto',
WebkitOverflowScrolling: 'touch',
touchAction: 'pan-y',
borderTop: 0,
borderTopLeftRadius: 0,
borderTopRightRadius: 0
}}
onTouchMove={(e) => e.stopPropagation()}
>
{searchResults.length > 0 ? (
<Stack gap={0}>
{searchResults.map((option, index) => (
<Box
key={option.id}
style={{
cursor: 'pointer',
backgroundColor: selectedIndex === index ? 'var(--mantine-color-gray-1)' : 'transparent',
}}
onClick={() => handleOptionSelect(option)}
onMouseEnter={() => setSelectedIndex(index)}
>
{renderOption(option, selectedIndex === index)}
</Box>
))}
</Stack>
) : (
<Box p="md">
<Text size="sm" c="dimmed" ta="center">
{searchQuery.trim() ? 'No results found' : 'Start typing to search...'}
</Text>
</Box>
)}
</Paper>
)}
</Box>
);
};
export default Typeahead;

View File

@@ -1,21 +1,28 @@
import { createContext, PropsWithChildren, useCallback, useContext, useMemo } from "react";
import {
createContext,
PropsWithChildren,
useCallback,
useContext,
useMemo,
} from "react";
import { MantineColor, MantineColorScheme } from "@mantine/core";
import { useQueryClient } from "@tanstack/react-query";
import { Player } from "@/features/players/types";
import { playerKeys, playerQueries, useMe } from "@/features/players/queries";
interface AuthData {
user: Player | undefined;
metadata: { accentColor: MantineColor; colorScheme: MantineColorScheme };
roles: string[];
phone: string;
}
export const defaultAuthData: AuthData = {
user: undefined,
metadata: { accentColor: 'blue', colorScheme: 'auto' },
metadata: { accentColor: "blue", colorScheme: "dark" },
roles: [],
}
phone: ""
};
export interface AuthContextType extends AuthData {
set: ({ user, metadata, roles }: Partial<AuthContextType>) => void;
@@ -27,39 +34,46 @@ const AuthContext = createContext<AuthContextType>({
});
export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
const queryClient = useQueryClient();
const queryClient = useQueryClient();
const { data } = useMe();
const set = useCallback((updates: Partial<AuthData>) => {
queryClient.setQueryData(playerKeys.auth, (oldData: AuthData | undefined) => {
const currentData = oldData || defaultAuthData;
return {
...currentData,
...updates,
metadata: updates.metadata
? { ...currentData.metadata, ...updates.metadata }
: currentData.metadata
};
});
}, [queryClient]);
return (
<AuthContext
value={{
user: data?.user || defaultAuthData.user,
metadata: data?.metadata || defaultAuthData.metadata,
roles: data?.roles || defaultAuthData.roles,
set
}}>
{children}
</AuthContext>
)
const set = useCallback(
(updates: Partial<AuthData>) => {
queryClient.setQueryData(
playerKeys.auth,
(oldData: AuthData | undefined) => {
const currentData = oldData || defaultAuthData;
return {
...currentData,
...updates,
metadata: updates.metadata
? { ...currentData.metadata, ...updates.metadata }
: currentData.metadata,
};
}
);
},
[queryClient]
);
const value = useMemo(
() => ({
user: data?.user,
metadata: data?.metadata || { accentColor: "blue" as MantineColor, colorScheme: "dark" as MantineColorScheme },
roles: data?.roles || [],
phone: data?.phone || "",
set,
}),
[data, set]
);
return <AuthContext value={value}>{children}</AuthContext>;
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};

Some files were not shown because too many files have changed in this diff Show More