@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.
- package/dist/index.cjs +1233 -158
- package/dist/index.d.cts +1012 -106
- package/dist/index.d.ts +1012 -106
- package/dist/index.js +1117 -141
- package/package.json +1 -1
- package/prisma/migrations/20260104000000_add_player_number/migration.sql +23 -0
- package/prisma/migrations/20260104092800_add_location_model_and_tournament_fields/migration.sql +45 -0
- package/prisma/migrations/20260104210034_add_policy_acceptance_fields/migration.sql +19 -0
- package/prisma/migrations/20260104231435_split_entries_standings/migration.sql +137 -0
- package/prisma/migrations/20260105000000_add_oppr_ranking_models/migration.sql +108 -0
- package/prisma/schema.prisma +292 -37
- package/prisma/seed.ts +157 -48
package/prisma/schema.prisma
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
36
|
-
|
|
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([
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
81
|
-
model
|
|
82
|
-
id
|
|
83
|
-
createdAt
|
|
84
|
-
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
|
-
|
|
88
|
-
|
|
89
|
-
tournamentId
|
|
90
|
-
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
optedOut Boolean @default(false)
|
|
121
|
+
number Int? // Match number within the round
|
|
122
|
+
machineName String? // Machine played on
|
|
95
123
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
dynamicPoints Float? @default(0) // Dynamic distribution points
|
|
99
|
-
totalPoints Float? // Total points (linear + dynamic)
|
|
124
|
+
// Relations
|
|
125
|
+
entries Entry[]
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
decayedPoints Float? // Points after applying decay (defaults to totalPoints via application logic)
|
|
127
|
+
@@index([tournamentId])
|
|
128
|
+
@@index([roundId])
|
|
129
|
+
}
|
|
105
130
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
+
}
|