@opprs/db-prisma 2.2.1 → 2.5.0

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
@@ -49,9 +45,17 @@ model Tournament {
49
45
  // Tournament identification
50
46
  externalId String? @unique // External ID from OPPR or other systems
51
47
  name String
52
- location String?
48
+ description String? @db.VarChar(2000)
53
49
  date DateTime
54
50
 
51
+ // Location relation
52
+ locationId String?
53
+ location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
54
+
55
+ // Organizer relation
56
+ organizerId String?
57
+ organizer Player? @relation("OrganizedTournaments", fields: [organizerId], references: [id], onDelete: SetNull)
58
+
55
59
  // Tournament configuration (stored as JSON)
56
60
  // Contains TGPConfig structure from OPPR
57
61
  tgpConfig Json?
@@ -70,48 +74,117 @@ model Tournament {
70
74
  firstPlaceValue Float?
71
75
 
72
76
  // Relations
73
- results TournamentResult[]
77
+ rounds Round[]
78
+ matches Match[]
79
+ standings Standing[]
80
+ rankingHistoryRecords OpprRankingHistory[]
74
81
 
75
82
  @@index([date])
76
83
  @@index([eventBooster])
77
84
  @@index([externalId])
85
+ @@index([locationId])
86
+ @@index([organizerId])
78
87
  }
79
88
 
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
89
+ // Round model - groups matches within a tournament stage
90
+ model Round {
91
+ id String @id @default(cuid())
92
+ createdAt DateTime @default(now())
93
+ updatedAt DateTime @updatedAt
94
+
95
+ tournamentId String
96
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
97
+
98
+ number Int // Round number within the stage (1, 2, 3...)
99
+ name String? // Optional name (e.g., "Quarterfinals", "Semifinal")
100
+ isFinals Boolean @default(false)
85
101
 
86
102
  // 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)
103
+ matches Match[]
104
+
105
+ @@unique([tournamentId, number, isFinals])
106
+ @@index([tournamentId])
107
+ @@index([tournamentId, isFinals])
108
+ }
109
+
110
+ // Match model - a single game with 1-4 players
111
+ model Match {
112
+ id String @id @default(cuid())
113
+ createdAt DateTime @default(now())
114
+ updatedAt DateTime @updatedAt
115
+
116
+ tournamentId String
117
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
118
+ roundId String?
119
+ round Round? @relation(fields: [roundId], references: [id], onDelete: SetNull)
91
120
 
92
- // Result data
93
- position Int // Finishing position (1 = first place)
94
- optedOut Boolean @default(false)
121
+ number Int? // Match number within the round
122
+ machineName String? // Machine played on
95
123
 
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)
124
+ // Relations
125
+ entries Entry[]
100
126
 
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)
127
+ @@index([tournamentId])
128
+ @@index([roundId])
129
+ }
105
130
 
106
- // Efficiency tracking
107
- efficiency Float? // Performance efficiency percentage
131
+ // Entry model - a player's participation in a match
132
+ model Entry {
133
+ id String @id @default(cuid())
134
+ createdAt DateTime @default(now())
135
+ updatedAt DateTime @updatedAt
108
136
 
109
- @@unique([playerId, tournamentId])
137
+ matchId String
138
+ match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
139
+ playerId String
140
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
141
+
142
+ result MatchResult // WIN, LOSS, TIE
143
+ position Int? // Position within the match (1st, 2nd, 3rd, 4th for group games)
144
+
145
+ @@unique([matchId, playerId])
146
+ @@index([matchId])
147
+ @@index([playerId])
148
+ }
149
+
150
+ // Standing model - final position for qualifying or finals
151
+ model Standing {
152
+ id String @id @default(cuid())
153
+ createdAt DateTime @default(now())
154
+ updatedAt DateTime @updatedAt
155
+
156
+ tournamentId String
157
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
158
+ playerId String
159
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
160
+
161
+ position Int // Finishing position (1 = first place)
162
+ isFinals Boolean @default(false)
163
+ optedOut Boolean @default(false)
164
+
165
+ // Points (calculated from merged standings)
166
+ linearPoints Float? @default(0)
167
+ dynamicPoints Float? @default(0)
168
+ totalPoints Float?
169
+ ageInDays Int? @default(0)
170
+ decayMultiplier Float? @default(1.0)
171
+ decayedPoints Float?
172
+ efficiency Float?
173
+
174
+ @@unique([playerId, tournamentId, isFinals])
110
175
  @@index([playerId])
111
176
  @@index([tournamentId])
177
+ @@index([tournamentId, isFinals])
112
178
  @@index([position])
113
179
  }
114
180
 
181
+ // Enum for match results
182
+ enum MatchResult {
183
+ WIN
184
+ LOSS
185
+ TIE
186
+ }
187
+
115
188
  // Enum for event booster types
116
189
  enum EventBoosterType {
117
190
  NONE
@@ -127,6 +200,12 @@ enum Role {
127
200
  ADMIN
128
201
  }
129
202
 
203
+ // Enum for blog post status
204
+ enum PostStatus {
205
+ DRAFT
206
+ PUBLISHED
207
+ }
208
+
130
209
  // User model - represents an authenticated user account
131
210
  model User {
132
211
  id String @id @default(cuid())
@@ -147,5 +226,181 @@ model User {
147
226
  // Session management (for token revocation)
148
227
  refreshTokenHash String?
149
228
 
229
+ // Policy acceptance timestamps (null = not accepted)
230
+ tosAcceptedAt DateTime?
231
+ privacyPolicyAcceptedAt DateTime?
232
+ codeOfConductAcceptedAt DateTime?
233
+
234
+ // Blog posts authored by this user
235
+ blogPosts BlogPost[]
236
+
237
+ // API Keys
238
+ apiKeys ApiKey[]
239
+
150
240
  @@index([email])
151
241
  }
242
+
243
+ // ApiKey model - represents an API key for programmatic access
244
+ model ApiKey {
245
+ id String @id @default(cuid())
246
+ createdAt DateTime @default(now())
247
+ updatedAt DateTime @updatedAt
248
+
249
+ // Key identification
250
+ name String // User-provided name (e.g., "CI Pipeline", "Mobile App")
251
+ keyPrefix String // First 14 characters of the key for display/lookup
252
+ keyHash String // bcrypt hash of the full key
253
+
254
+ // Ownership
255
+ userId String
256
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
257
+
258
+ // Lifecycle
259
+ expiresAt DateTime? // Optional expiration date
260
+ lastUsedAt DateTime? // Updated on each successful authentication
261
+
262
+ @@index([userId])
263
+ @@index([keyPrefix])
264
+ }
265
+
266
+ // Location model - represents a venue where tournaments are held
267
+ model Location {
268
+ id String @id @default(cuid())
269
+ createdAt DateTime @default(now())
270
+ updatedAt DateTime @updatedAt
271
+
272
+ externalId String? @unique
273
+ name String
274
+ address String?
275
+ city String?
276
+ state String?
277
+ country String?
278
+
279
+ // Relations
280
+ tournaments Tournament[]
281
+
282
+ @@index([externalId])
283
+ @@index([name])
284
+ @@index([city])
285
+ }
286
+
287
+ // BlogPost model - represents a blog article
288
+ model BlogPost {
289
+ id String @id @default(cuid())
290
+ createdAt DateTime @default(now())
291
+ updatedAt DateTime @updatedAt
292
+
293
+ // Content
294
+ title String
295
+ slug String @unique
296
+ content String @db.Text
297
+ excerpt String? @db.VarChar(500)
298
+
299
+ // Status
300
+ status PostStatus @default(DRAFT)
301
+ publishedAt DateTime?
302
+
303
+ // Featured image (external URL)
304
+ featuredImageUrl String?
305
+ featuredImageAlt String?
306
+
307
+ // SEO fields
308
+ metaTitle String? @db.VarChar(60)
309
+ metaDescription String? @db.VarChar(160)
310
+ ogTitle String?
311
+ ogDescription String?
312
+ ogImageUrl String?
313
+
314
+ // Author (User who created the post)
315
+ authorId String
316
+ author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
317
+
318
+ // Tags relation
319
+ tags BlogTag[]
320
+
321
+ @@index([slug])
322
+ @@index([status])
323
+ @@index([publishedAt])
324
+ @@index([authorId])
325
+ }
326
+
327
+ // BlogTag model - for categorizing blog posts
328
+ model BlogTag {
329
+ id String @id @default(cuid())
330
+ createdAt DateTime @default(now())
331
+ updatedAt DateTime @updatedAt
332
+
333
+ name String @unique
334
+ slug String @unique
335
+ description String?
336
+
337
+ // Posts with this tag
338
+ posts BlogPost[]
339
+
340
+ @@index([slug])
341
+ }
342
+
343
+ // OPPR Player Ranking - OPPR-specific rating and ranking data
344
+ // Separated from Player to allow for future alternative ranking systems
345
+ model OpprPlayerRanking {
346
+ id String @id @default(cuid())
347
+ createdAt DateTime @default(now())
348
+ updatedAt DateTime @updatedAt
349
+
350
+ // Relation to Player (one-to-one)
351
+ playerId String @unique
352
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
353
+
354
+ // Glicko Rating fields
355
+ rating Float @default(1500) // Glicko rating
356
+ ratingDeviation Float @default(200) // Rating uncertainty (RD)
357
+ lastRatingUpdate DateTime @default(now())
358
+
359
+ // World Ranking fields
360
+ ranking Int? // World ranking position (1 = best)
361
+ isRated Boolean @default(false) // Has 5+ events (eligible for ranking)
362
+
363
+ // Relations
364
+ history OpprRankingHistory[]
365
+
366
+ @@index([playerId])
367
+ @@index([rating])
368
+ @@index([ranking])
369
+ @@index([isRated])
370
+ }
371
+
372
+ // OPPR Ranking History - Historical record of ranking/rating changes
373
+ model OpprRankingHistory {
374
+ id String @id @default(cuid())
375
+ createdAt DateTime @default(now())
376
+
377
+ // Relation to OpprPlayerRanking
378
+ opprPlayerRankingId String
379
+ opprPlayerRanking OpprPlayerRanking @relation(fields: [opprPlayerRankingId], references: [id], onDelete: Cascade)
380
+
381
+ // Snapshot of values at this point in time
382
+ rating Float
383
+ ratingDeviation Float
384
+ ranking Int?
385
+ isRated Boolean
386
+
387
+ // Context for the change
388
+ changeType OpprRankingChangeType
389
+ tournamentId String?
390
+ tournament Tournament? @relation(fields: [tournamentId], references: [id], onDelete: SetNull)
391
+ notes String? @db.VarChar(500)
392
+
393
+ @@index([opprPlayerRankingId])
394
+ @@index([createdAt])
395
+ @@index([tournamentId])
396
+ @@index([opprPlayerRankingId, createdAt])
397
+ }
398
+
399
+ // Enum for tracking what caused the ranking change
400
+ enum OpprRankingChangeType {
401
+ INITIAL // First ranking record created
402
+ TOURNAMENT_RESULT // Rating updated after tournament
403
+ RANKING_REFRESH // Periodic ranking recalculation
404
+ RD_DECAY // RD increased due to inactivity
405
+ MANUAL_ADJUSTMENT // Administrative correction
406
+ }