@opprs/db-prisma 2.2.1-canary.3bbfda2 → 2.2.1-canary.4bd4fa2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,26 +18,22 @@ model Player {
18
18
 
19
19
  // Player identification
20
20
  externalId String? @unique // External ID from OPPR or other systems
21
+ playerNumber Int @unique // 5-digit unique identifier (10000-99999)
21
22
  name String?
22
23
 
23
- // OPPR Rating fields
24
- rating Float @default(1500) // Glicko rating
25
- ratingDeviation Float @default(200) // Rating uncertainty (RD)
26
- ranking Int? // World ranking position
27
- isRated Boolean @default(false) // Has 5+ events
24
+ // General player statistics
28
25
  eventCount Int @default(0) // Number of events participated
29
-
30
- // Timestamps for rating calculations
31
- lastRatingUpdate DateTime @default(now())
32
26
  lastEventDate DateTime?
33
27
 
34
28
  // Relations
35
- tournamentResults TournamentResult[]
36
- user User?
29
+ standings Standing[]
30
+ entries Entry[]
31
+ user User?
32
+ organizedTournaments Tournament[] @relation("OrganizedTournaments")
33
+ opprRanking OpprPlayerRanking?
37
34
 
38
35
  @@index([externalId])
39
- @@index([rating])
40
- @@index([ranking])
36
+ @@index([playerNumber])
41
37
  }
42
38
 
43
39
  // Tournament model - represents a pinball tournament event
@@ -48,16 +44,26 @@ model Tournament {
48
44
 
49
45
  // Tournament identification
50
46
  externalId String? @unique // External ID from OPPR or other systems
47
+ externalUrl String? // URL to external tournament page (e.g., IFPA, Matchplay)
51
48
  name String
52
- location String?
49
+ description String? @db.VarChar(2000)
53
50
  date DateTime
54
51
 
52
+ // Location relation
53
+ locationId String?
54
+ location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
55
+
56
+ // Organizer relation
57
+ organizerId String?
58
+ organizer Player? @relation("OrganizedTournaments", fields: [organizerId], references: [id], onDelete: SetNull)
59
+
55
60
  // Tournament configuration (stored as JSON)
56
61
  // Contains TGPConfig structure from OPPR
57
62
  tgpConfig Json?
58
63
 
59
64
  // Event classification
60
65
  eventBooster EventBoosterType @default(NONE)
66
+ qualifyingFormat TournamentFormatType @default(NONE)
61
67
  allowsOptOut Boolean @default(false)
62
68
 
63
69
  // Tournament value calculations (can be calculated or cached)
@@ -70,48 +76,117 @@ model Tournament {
70
76
  firstPlaceValue Float?
71
77
 
72
78
  // Relations
73
- results TournamentResult[]
79
+ rounds Round[]
80
+ matches Match[]
81
+ standings Standing[]
82
+ rankingHistoryRecords OpprRankingHistory[]
74
83
 
75
84
  @@index([date])
76
85
  @@index([eventBooster])
77
86
  @@index([externalId])
87
+ @@index([locationId])
88
+ @@index([organizerId])
78
89
  }
79
90
 
80
- // Tournament Result - junction table linking players to tournaments
81
- model TournamentResult {
82
- id String @id @default(cuid())
83
- createdAt DateTime @default(now())
84
- updatedAt DateTime @updatedAt
91
+ // Round model - groups matches within a tournament stage
92
+ model Round {
93
+ id String @id @default(cuid())
94
+ createdAt DateTime @default(now())
95
+ updatedAt DateTime @updatedAt
96
+
97
+ tournamentId String
98
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
99
+
100
+ number Int // Round number within the stage (1, 2, 3...)
101
+ name String? // Optional name (e.g., "Quarterfinals", "Semifinal")
102
+ isFinals Boolean @default(false)
85
103
 
86
104
  // Relations
87
- playerId String
88
- player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
89
- tournamentId String
90
- tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
105
+ matches Match[]
106
+
107
+ @@unique([tournamentId, number, isFinals])
108
+ @@index([tournamentId])
109
+ @@index([tournamentId, isFinals])
110
+ }
91
111
 
92
- // Result data
93
- position Int // Finishing position (1 = first place)
94
- optedOut Boolean @default(false)
112
+ // Match model - a single game with 1-4 players
113
+ model Match {
114
+ id String @id @default(cuid())
115
+ createdAt DateTime @default(now())
116
+ updatedAt DateTime @updatedAt
95
117
 
96
- // Points awarded
97
- linearPoints Float? @default(0) // Linear distribution points
98
- dynamicPoints Float? @default(0) // Dynamic distribution points
99
- totalPoints Float? // Total points (linear + dynamic)
118
+ tournamentId String
119
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
120
+ roundId String?
121
+ round Round? @relation(fields: [roundId], references: [id], onDelete: SetNull)
122
+
123
+ number Int? // Match number within the round
124
+ machineName String? // Machine played on
125
+
126
+ // Relations
127
+ entries Entry[]
128
+
129
+ @@index([tournamentId])
130
+ @@index([roundId])
131
+ }
100
132
 
101
- // Time decay tracking
102
- ageInDays Int? @default(0) // Age of event in days at calculation time
103
- decayMultiplier Float? @default(1.0) // Time decay multiplier (1.0, 0.75, 0.5, or 0.0)
104
- decayedPoints Float? // Points after applying decay (defaults to totalPoints via application logic)
133
+ // Entry model - a player's participation in a match
134
+ model Entry {
135
+ id String @id @default(cuid())
136
+ createdAt DateTime @default(now())
137
+ updatedAt DateTime @updatedAt
105
138
 
106
- // Efficiency tracking
107
- efficiency Float? // Performance efficiency percentage
139
+ matchId String
140
+ match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
141
+ playerId String
142
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
108
143
 
109
- @@unique([playerId, tournamentId])
144
+ result MatchResult // WIN, LOSS, TIE
145
+ position Int? // Position within the match (1st, 2nd, 3rd, 4th for group games)
146
+
147
+ @@unique([matchId, playerId])
148
+ @@index([matchId])
149
+ @@index([playerId])
150
+ }
151
+
152
+ // Standing model - final position for qualifying or finals
153
+ model Standing {
154
+ id String @id @default(cuid())
155
+ createdAt DateTime @default(now())
156
+ updatedAt DateTime @updatedAt
157
+
158
+ tournamentId String
159
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
160
+ playerId String
161
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
162
+
163
+ position Int // Finishing position (1 = first place)
164
+ isFinals Boolean @default(false)
165
+ optedOut Boolean @default(false)
166
+
167
+ // Points (calculated from merged standings)
168
+ linearPoints Float? @default(0)
169
+ dynamicPoints Float? @default(0)
170
+ totalPoints Float?
171
+ ageInDays Int? @default(0)
172
+ decayMultiplier Float? @default(1.0)
173
+ decayedPoints Float?
174
+ efficiency Float?
175
+
176
+ @@unique([playerId, tournamentId, isFinals])
110
177
  @@index([playerId])
111
178
  @@index([tournamentId])
179
+ @@index([tournamentId, isFinals])
112
180
  @@index([position])
113
181
  }
114
182
 
183
+ // Enum for match results
184
+ enum MatchResult {
185
+ WIN
186
+ LOSS
187
+ TIE
188
+ }
189
+
115
190
  // Enum for event booster types
116
191
  enum EventBoosterType {
117
192
  NONE
@@ -121,12 +196,33 @@ enum EventBoosterType {
121
196
  MAJOR
122
197
  }
123
198
 
199
+ // Enum for tournament format types (qualifying and finals)
200
+ enum TournamentFormatType {
201
+ SINGLE_ELIMINATION
202
+ DOUBLE_ELIMINATION
203
+ MATCH_PLAY
204
+ BEST_GAME
205
+ CARD_QUALIFYING
206
+ PIN_GOLF
207
+ FLIP_FRENZY
208
+ STRIKE_FORMAT
209
+ TARGET_MATCH_PLAY
210
+ HYBRID
211
+ NONE
212
+ }
213
+
124
214
  // Enum for user roles
125
215
  enum Role {
126
216
  USER
127
217
  ADMIN
128
218
  }
129
219
 
220
+ // Enum for blog post status
221
+ enum PostStatus {
222
+ DRAFT
223
+ PUBLISHED
224
+ }
225
+
130
226
  // User model - represents an authenticated user account
131
227
  model User {
132
228
  id String @id @default(cuid())
@@ -147,5 +243,181 @@ model User {
147
243
  // Session management (for token revocation)
148
244
  refreshTokenHash String?
149
245
 
246
+ // Policy acceptance timestamps (null = not accepted)
247
+ tosAcceptedAt DateTime?
248
+ privacyPolicyAcceptedAt DateTime?
249
+ codeOfConductAcceptedAt DateTime?
250
+
251
+ // Blog posts authored by this user
252
+ blogPosts BlogPost[]
253
+
254
+ // API Keys
255
+ apiKeys ApiKey[]
256
+
150
257
  @@index([email])
151
258
  }
259
+
260
+ // ApiKey model - represents an API key for programmatic access
261
+ model ApiKey {
262
+ id String @id @default(cuid())
263
+ createdAt DateTime @default(now())
264
+ updatedAt DateTime @updatedAt
265
+
266
+ // Key identification
267
+ name String // User-provided name (e.g., "CI Pipeline", "Mobile App")
268
+ keyPrefix String // First 14 characters of the key for display/lookup
269
+ keyHash String // bcrypt hash of the full key
270
+
271
+ // Ownership
272
+ userId String
273
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
274
+
275
+ // Lifecycle
276
+ expiresAt DateTime? // Optional expiration date
277
+ lastUsedAt DateTime? // Updated on each successful authentication
278
+
279
+ @@index([userId])
280
+ @@index([keyPrefix])
281
+ }
282
+
283
+ // Location model - represents a venue where tournaments are held
284
+ model Location {
285
+ id String @id @default(cuid())
286
+ createdAt DateTime @default(now())
287
+ updatedAt DateTime @updatedAt
288
+
289
+ externalId String? @unique
290
+ name String
291
+ address String?
292
+ city String?
293
+ state String?
294
+ country String?
295
+
296
+ // Relations
297
+ tournaments Tournament[]
298
+
299
+ @@index([externalId])
300
+ @@index([name])
301
+ @@index([city])
302
+ }
303
+
304
+ // BlogPost model - represents a blog article
305
+ model BlogPost {
306
+ id String @id @default(cuid())
307
+ createdAt DateTime @default(now())
308
+ updatedAt DateTime @updatedAt
309
+
310
+ // Content
311
+ title String
312
+ slug String @unique
313
+ content String @db.Text
314
+ excerpt String? @db.VarChar(500)
315
+
316
+ // Status
317
+ status PostStatus @default(DRAFT)
318
+ publishedAt DateTime?
319
+
320
+ // Featured image (external URL)
321
+ featuredImageUrl String?
322
+ featuredImageAlt String?
323
+
324
+ // SEO fields
325
+ metaTitle String? @db.VarChar(60)
326
+ metaDescription String? @db.VarChar(160)
327
+ ogTitle String?
328
+ ogDescription String?
329
+ ogImageUrl String?
330
+
331
+ // Author (User who created the post)
332
+ authorId String
333
+ author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
334
+
335
+ // Tags relation
336
+ tags BlogTag[]
337
+
338
+ @@index([slug])
339
+ @@index([status])
340
+ @@index([publishedAt])
341
+ @@index([authorId])
342
+ }
343
+
344
+ // BlogTag model - for categorizing blog posts
345
+ model BlogTag {
346
+ id String @id @default(cuid())
347
+ createdAt DateTime @default(now())
348
+ updatedAt DateTime @updatedAt
349
+
350
+ name String @unique
351
+ slug String @unique
352
+ description String?
353
+
354
+ // Posts with this tag
355
+ posts BlogPost[]
356
+
357
+ @@index([slug])
358
+ }
359
+
360
+ // OPPR Player Ranking - OPPR-specific rating and ranking data
361
+ // Separated from Player to allow for future alternative ranking systems
362
+ model OpprPlayerRanking {
363
+ id String @id @default(cuid())
364
+ createdAt DateTime @default(now())
365
+ updatedAt DateTime @updatedAt
366
+
367
+ // Relation to Player (one-to-one)
368
+ playerId String @unique
369
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
370
+
371
+ // Glicko Rating fields
372
+ rating Float @default(1500) // Glicko rating
373
+ ratingDeviation Float @default(200) // Rating uncertainty (RD)
374
+ lastRatingUpdate DateTime @default(now())
375
+
376
+ // World Ranking fields
377
+ ranking Int? // World ranking position (1 = best)
378
+ isRated Boolean @default(false) // Has 5+ events (eligible for ranking)
379
+
380
+ // Relations
381
+ history OpprRankingHistory[]
382
+
383
+ @@index([playerId])
384
+ @@index([rating])
385
+ @@index([ranking])
386
+ @@index([isRated])
387
+ }
388
+
389
+ // OPPR Ranking History - Historical record of ranking/rating changes
390
+ model OpprRankingHistory {
391
+ id String @id @default(cuid())
392
+ createdAt DateTime @default(now())
393
+
394
+ // Relation to OpprPlayerRanking
395
+ opprPlayerRankingId String
396
+ opprPlayerRanking OpprPlayerRanking @relation(fields: [opprPlayerRankingId], references: [id], onDelete: Cascade)
397
+
398
+ // Snapshot of values at this point in time
399
+ rating Float
400
+ ratingDeviation Float
401
+ ranking Int?
402
+ isRated Boolean
403
+
404
+ // Context for the change
405
+ changeType OpprRankingChangeType
406
+ tournamentId String?
407
+ tournament Tournament? @relation(fields: [tournamentId], references: [id], onDelete: SetNull)
408
+ notes String? @db.VarChar(500)
409
+
410
+ @@index([opprPlayerRankingId])
411
+ @@index([createdAt])
412
+ @@index([tournamentId])
413
+ @@index([opprPlayerRankingId, createdAt])
414
+ }
415
+
416
+ // Enum for tracking what caused the ranking change
417
+ enum OpprRankingChangeType {
418
+ INITIAL // First ranking record created
419
+ TOURNAMENT_RESULT // Rating updated after tournament
420
+ RANKING_REFRESH // Periodic ranking recalculation
421
+ RD_DECAY // RD increased due to inactivity
422
+ MANUAL_ADJUSTMENT // Administrative correction
423
+ }