crazy-odds-bet-shared 1.0.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.
@@ -0,0 +1,218 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const {
3
+ createBaseSchema,
4
+ baseSchemaOptions,
5
+ addExternalRefsSlugHook
6
+ } = require('./base');
7
+
8
+ /**
9
+ * Fixture status enum
10
+ */
11
+ const FixtureStatus = {
12
+ SCHEDULED: 'scheduled',
13
+ LIVE: 'live',
14
+ FINISHED: 'finished',
15
+ POSTPONED: 'postponed',
16
+ CANCELLED: 'cancelled'
17
+ };
18
+
19
+ /**
20
+ * Fixture schema
21
+ */
22
+ const fixtureSchema = new Schema(
23
+ {
24
+ ...createBaseSchema(),
25
+ sportId: { type: String, required: true, ref: 'Sport' },
26
+ competitionId: { type: String, required: true, ref: 'Competition' },
27
+ resourceId: { type: String },
28
+ homeTeamId: { type: String, required: true, ref: 'Team' },
29
+ awayTeamId: { type: String, required: true, ref: 'Team' },
30
+ name: { type: String, required: true },
31
+ slug: { type: String, unique: true, sparse: true },
32
+ scheduledAt: { type: Date, required: true },
33
+ status: {
34
+ type: String,
35
+ enum: Object.values(FixtureStatus),
36
+ default: FixtureStatus.SCHEDULED
37
+ },
38
+ externalRefs: { type: Schema.Types.Mixed, default: {} },
39
+ metadata: { type: Schema.Types.Mixed, default: {} }
40
+ },
41
+ baseSchemaOptions
42
+ );
43
+
44
+ // Indexes
45
+ fixtureSchema.index({ sportId: 1 });
46
+ fixtureSchema.index({ competitionId: 1 });
47
+ fixtureSchema.index({ homeTeamId: 1 });
48
+ fixtureSchema.index({ awayTeamId: 1 });
49
+ fixtureSchema.index({ scheduledAt: 1 });
50
+ fixtureSchema.index({ status: 1 });
51
+ fixtureSchema.index({ scheduledAt: 1, status: 1 });
52
+
53
+ // Add auto-slug generation for external refs
54
+ addExternalRefsSlugHook(fixtureSchema, 'externalRefs');
55
+
56
+ // Virtuals
57
+ fixtureSchema.virtual('sport', {
58
+ ref: 'Sport',
59
+ localField: 'sportId',
60
+ foreignField: '_id',
61
+ justOne: true
62
+ });
63
+
64
+ fixtureSchema.virtual('competition', {
65
+ ref: 'Competition',
66
+ localField: 'competitionId',
67
+ foreignField: '_id',
68
+ justOne: true
69
+ });
70
+
71
+ fixtureSchema.virtual('homeTeam', {
72
+ ref: 'Team',
73
+ localField: 'homeTeamId',
74
+ foreignField: '_id',
75
+ justOne: true
76
+ });
77
+
78
+ fixtureSchema.virtual('awayTeam', {
79
+ ref: 'Team',
80
+ localField: 'awayTeamId',
81
+ foreignField: '_id',
82
+ justOne: true
83
+ });
84
+
85
+ // Static methods
86
+ fixtureSchema.static('findBySlug', function (slug) {
87
+ return this.findOne({ slug });
88
+ });
89
+
90
+ fixtureSchema.static('getActive', function () {
91
+ return this.find({
92
+ status: { $in: [FixtureStatus.SCHEDULED, FixtureStatus.LIVE] }
93
+ }).sort({ scheduledAt: 1 });
94
+ });
95
+
96
+ fixtureSchema.static('findByExternalRef', function (bookmakerSlug, sportId, competitionId, externalId) {
97
+ const query = {};
98
+ query[`externalRefs.${bookmakerSlug}.id`] = externalId;
99
+ query['sportId'] = sportId;
100
+ query['competitionId'] = competitionId;
101
+ return this.findOne(query);
102
+ });
103
+
104
+ fixtureSchema.static('findByStatus', function (status) {
105
+ return this.find({ status }).sort({ scheduledAt: 1 });
106
+ });
107
+
108
+ fixtureSchema.static('findBySport', function (sportId) {
109
+ return this.find({ sportId }).sort({ scheduledAt: 1 });
110
+ });
111
+
112
+ fixtureSchema.static('findByCompetition', function (competitionId) {
113
+ return this.find({ competitionId }).sort({ scheduledAt: 1 });
114
+ });
115
+
116
+ fixtureSchema.static('findByTeam', function (teamId) {
117
+ return this.find({
118
+ $or: [{ homeTeamId: teamId }, { awayTeamId: teamId }]
119
+ }).sort({ scheduledAt: 1 });
120
+ });
121
+
122
+ fixtureSchema.static('findUpcoming', function (limit = 20) {
123
+ return this.find({
124
+ scheduledAt: { $gte: new Date() },
125
+ status: FixtureStatus.SCHEDULED
126
+ }).sort({ scheduledAt: 1 }).limit(limit);
127
+ });
128
+
129
+ fixtureSchema.static('findByDateRange', function (startDate, endDate) {
130
+ return this.find({
131
+ scheduledAt: { $gte: startDate, $lte: endDate }
132
+ }).sort({ scheduledAt: 1 });
133
+ });
134
+
135
+ // Instance methods
136
+ fixtureSchema.method('activate', async function () {
137
+ this.status = FixtureStatus.SCHEDULED;
138
+ return this.save();
139
+ });
140
+
141
+ fixtureSchema.method('deactivate', async function () {
142
+ this.status = FixtureStatus.CANCELLED;
143
+ return this.save();
144
+ });
145
+
146
+ fixtureSchema.method('addExternalRef', async function (bookmakerSlug, externalId, data) {
147
+ // Convert array to object if needed (migration from old format)
148
+ if (Array.isArray(this.externalRefs)) {
149
+ this.externalRefs = {};
150
+ }
151
+ if (!this.externalRefs) {
152
+ this.externalRefs = {};
153
+ }
154
+ this.externalRefs[bookmakerSlug] = {
155
+ id: externalId,
156
+ name: data.name || '',
157
+ slug: data.slug || bookmakerSlug,
158
+ isActive: data.isActive !== undefined ? data.isActive : true,
159
+ metadata: data.metadata || {}
160
+ };
161
+ this.markModified('externalRefs');
162
+ return this.save();
163
+ });
164
+
165
+ fixtureSchema.method('getExternalRef', function (bookmakerSlug) {
166
+ return this.externalRefs?.[bookmakerSlug] || null;
167
+ });
168
+
169
+ fixtureSchema.method('removeExternalRef', async function (bookmakerSlug) {
170
+ if (this.externalRefs?.[bookmakerSlug]) {
171
+ delete this.externalRefs[bookmakerSlug];
172
+ this.markModified('externalRefs');
173
+ return this.save();
174
+ }
175
+ return this;
176
+ });
177
+
178
+ fixtureSchema.method('activateExternalRef', async function (bookmakerSlug) {
179
+ if (this.externalRefs?.[bookmakerSlug]) {
180
+ this.externalRefs[bookmakerSlug].isActive = true;
181
+ this.markModified('externalRefs');
182
+ return this.save();
183
+ }
184
+ return this;
185
+ });
186
+
187
+ fixtureSchema.method('deactivateExternalRef', async function (bookmakerSlug) {
188
+ if (this.externalRefs?.[bookmakerSlug]) {
189
+ this.externalRefs[bookmakerSlug].isActive = false;
190
+ this.markModified('externalRefs');
191
+ return this.save();
192
+ }
193
+ return this;
194
+ });
195
+
196
+ fixtureSchema.method('markAsLive', async function () {
197
+ this.status = FixtureStatus.LIVE;
198
+ return this.save();
199
+ });
200
+
201
+ fixtureSchema.method('markAsFinished', async function () {
202
+ this.status = FixtureStatus.FINISHED;
203
+ return this.save();
204
+ });
205
+
206
+ fixtureSchema.method('markAsPostponed', async function () {
207
+ this.status = FixtureStatus.POSTPONED;
208
+ return this.save();
209
+ });
210
+
211
+ fixtureSchema.method('markAsCancelled', async function () {
212
+ this.status = FixtureStatus.CANCELLED;
213
+ return this.save();
214
+ });
215
+
216
+ const Fixture = model('Fixture', fixtureSchema);
217
+
218
+ module.exports = { Fixture, FixtureStatus };
@@ -0,0 +1,30 @@
1
+ const { Bookmaker } = require('./bookmaker');
2
+ const { Sport } = require('./sport');
3
+ const { Competition } = require('./competition');
4
+ const { Team } = require('./team');
5
+ const { Player } = require('./player');
6
+ const { Fixture, FixtureStatus } = require('./fixture');
7
+ const { User } = require('./user');
8
+ const { MarketTemplate } = require('./marketTemplate');
9
+ const { Market } = require('./market');
10
+ const { Odd } = require('./odd');
11
+ const { OddDebug } = require('./oddDebug');
12
+ const { BetTracker } = require('./betTracker');
13
+ const { generateSlug } = require('./base');
14
+
15
+ module.exports = {
16
+ Bookmaker,
17
+ Sport,
18
+ Competition,
19
+ Team,
20
+ Player,
21
+ Fixture,
22
+ FixtureStatus,
23
+ User,
24
+ MarketTemplate,
25
+ Market,
26
+ Odd,
27
+ OddDebug,
28
+ BetTracker,
29
+ generateSlug
30
+ };
@@ -0,0 +1,38 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * Market schema - represents a priced market instance tied to a fixture/template
6
+ */
7
+ const marketSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+
11
+ fixtureId: { type: String, required: true, ref: 'Fixture' },
12
+ marketTemplateId: { type: String, required: true, ref: 'MarketTemplate' },
13
+ marketTemplateName: { type: String, required: true },
14
+ specifiers: {
15
+ type: Schema.Types.Mixed,
16
+ default: {}
17
+ }
18
+ },
19
+ baseSchemaOptions
20
+ );
21
+
22
+ // Indexes to speed up lookups by fixture and template
23
+ marketSchema.index({ fixtureId: 1 });
24
+ marketSchema.index({ marketTemplateId: 1 });
25
+ marketSchema.index({ fixtureId: 1, marketTemplateId: 1 });
26
+
27
+ // Static methods
28
+ marketSchema.static('findByFixtureAndMarketTemplate', function (fixtureId, marketTemplateId) {
29
+ return this.findOne({ fixtureId, marketTemplateId });
30
+ });
31
+
32
+ marketSchema.static('findBySpecifiers', function (fixtureId, marketTemplateId, specifiers) {
33
+ return this.findOne({ fixtureId, marketTemplateId, specifiers });
34
+ });
35
+
36
+ const Market = model('Market', marketSchema);
37
+
38
+ module.exports = { Market };
@@ -0,0 +1,102 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * Market Template schema - defines standardized market types and bookmaker mappings
6
+ */
7
+ const marketTemplateSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+
11
+ sportId: { type: String, required: true, ref: 'Sport' },
12
+ name: { type: String, required: true },
13
+ slug: { type: String, unique: true, sparse: true },
14
+ category: {
15
+ type: String,
16
+ required: true,
17
+ enum: ['player-props', 'match-outcome', 'handicaps']
18
+ },
19
+
20
+ // Standardized outcome types for this market template
21
+ outcomes: [{
22
+ code: { type: String, required: true }, // 'over', 'under', '1', 'x', '2', etc.
23
+ name: { type: String, required: true } // Display name
24
+ }],
25
+
26
+ // Required specifier keys with their properties
27
+ // Format: { player: ['name', 'id'], total: ['value'] }
28
+ specifierKeys: {
29
+ type: Schema.Types.Mixed,
30
+ default: {}
31
+ },
32
+
33
+ // Bookmaker-specific mappings
34
+ externalRefs: {
35
+ type: Schema.Types.Mixed,
36
+ default: {}
37
+ /*
38
+ Structure per bookmaker:
39
+ {
40
+ superbet: {
41
+ marketId: 233565,
42
+ method: 'outcomeId', // How to identify outcome type
43
+ outcomes: [
44
+ { code: 'over', outcomeId: 5788 },
45
+ { code: 'under', outcomeId: 5787 }
46
+ ],
47
+ keys: {
48
+ player: { path: 'specifiers.player', idPath: 'extra.huddle-player-id' },
49
+ total: { path: 'specifiers.total' }
50
+ }
51
+ }
52
+ }
53
+ */
54
+ },
55
+
56
+ metadata: { type: Schema.Types.Mixed, default: {} }
57
+ },
58
+ baseSchemaOptions
59
+ );
60
+
61
+ // Indexes
62
+ marketTemplateSchema.index({ slug: 1 });
63
+ marketTemplateSchema.index({ category: 1 });
64
+ marketTemplateSchema.index({ sportId: 1 });
65
+ marketTemplateSchema.index({ 'externalRefs.superbet.marketId': 1 });
66
+
67
+ // Pre-save hook to auto-generate slug from name
68
+ marketTemplateSchema.pre('save', function (next) {
69
+ if (!this.slug && this.name) {
70
+ this.slug = this.name
71
+ .toLowerCase()
72
+ .trim()
73
+ .replace(/[^\w\s-]/g, '')
74
+ .replace(/\s+/g, '-')
75
+ .replace(/-+/g, '-');
76
+ }
77
+ next();
78
+ });
79
+
80
+ // Static methods
81
+ marketTemplateSchema.static('findBySport', function (sportId) {
82
+ return this.find({ sportId });
83
+ });
84
+
85
+ marketTemplateSchema.static('findBySlug', function (slug) {
86
+ return this.findOne({ slug });
87
+ });
88
+
89
+ marketTemplateSchema.static('findByBookmakerMarketId', function (bookmakerSlug, marketId) {
90
+ const query = {};
91
+ query[`externalRefs.${bookmakerSlug}.marketId`] = marketId;
92
+ return this.findOne(query);
93
+ });
94
+
95
+ marketTemplateSchema.static('findByCategory', function (category) {
96
+ return this.find({ category });
97
+ });
98
+
99
+ const MarketTemplate = model('MarketTemplate', marketTemplateSchema);
100
+
101
+ module.exports = { MarketTemplate };
102
+
package/models/odd.js ADDED
@@ -0,0 +1,78 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * Odd schema - represents a priced market instance tied to a fixture/template
6
+ */
7
+ const oddSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+
11
+ bookmakerId: { type: String, required: true, ref: 'Bookmaker' },
12
+ bookmakerName: { type: String },
13
+ fixtureId: { type: String, required: true, ref: 'Fixture' },
14
+ fixtureName: { type: String },
15
+ marketId: { type: String, required: true, ref: 'Market' },
16
+ marketTemplateName: { type: String },
17
+ externalMarketId: { type: String },
18
+ externalMarketUuid: { type: String },
19
+ externalMarketName: { type: String },
20
+ externalOutcomeId: { type: String },
21
+ externalOutcomeUuid: { type: String },
22
+ code: { type: String },
23
+ name: { type: String },
24
+ price: { type: Number, required: true },
25
+ status: { type: String, enum: ['active', 'suspended', 'settled', 'block'], default: 'active' }
26
+ },
27
+ baseSchemaOptions
28
+ );
29
+
30
+ // Indexes to speed up lookups by fixture and template
31
+ oddSchema.index({ bookmakerId: 1 });
32
+ oddSchema.index({ marketId: 1 });
33
+ oddSchema.index({ bookmakerId: 1, marketId: 1 });
34
+
35
+ // Virtuals
36
+ oddSchema.virtual('bookmaker', {
37
+ ref: 'Bookmaker',
38
+ localField: 'bookmakerId',
39
+ foreignField: '_id',
40
+ justOne: true
41
+ });
42
+
43
+ oddSchema.virtual('fixture', {
44
+ ref: 'Fixture',
45
+ localField: 'fixtureId',
46
+ foreignField: '_id',
47
+ justOne: true
48
+ });
49
+
50
+ oddSchema.virtual('market', {
51
+ ref: 'Market',
52
+ localField: 'marketId',
53
+ foreignField: '_id',
54
+ justOne: true
55
+ });
56
+
57
+ // Static methods
58
+ oddSchema.static('findByBookmakerAndMarket', function (bookmakerId, marketId) {
59
+ return this.findOne({ bookmakerId, marketId });
60
+ });
61
+
62
+ oddSchema.static('findByExternalOutcomeUuid', function (bookmakerId, externalOutcomeUuid) {
63
+ const query = {};
64
+ query['bookmakerId'] = bookmakerId;
65
+ query['externalOutcomeUuid'] = externalOutcomeUuid;
66
+ return this.findOne(query);
67
+ });
68
+
69
+ oddSchema.static('findByExternalOutcomeId', function (bookmakerId, externalOutcomeId) {
70
+ const query = {};
71
+ query['bookmakerId'] = bookmakerId;
72
+ query['externalOutcomeId'] = externalOutcomeId;
73
+ return this.findOne(query);
74
+ });
75
+
76
+ const Odd = model('Odd', oddSchema);
77
+
78
+ module.exports = { Odd };
@@ -0,0 +1,32 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * OddDebug schema - stores debugging information for odds
6
+ */
7
+ const oddDebugSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+
11
+ bookmakerId: { type: String, required: true, ref: 'Bookmaker' },
12
+ oddId: { type: String, required: true, ref: 'Odd' },
13
+ fixtureId: { type: String, required: true, ref: 'Fixture' },
14
+ debugData: { type: Schema.Types.Mixed, default: {} }
15
+ },
16
+ baseSchemaOptions
17
+ );
18
+
19
+ // Indexes to speed up lookups by fixture and template
20
+ oddDebugSchema.index({ bookmakerId: 1 });
21
+ oddDebugSchema.index({ oddId: 1 });
22
+ oddDebugSchema.index({ fixtureId: 1 });
23
+ oddDebugSchema.index({ bookmakerId: 1, oddId: 1, fixtureId: 1 }, { unique: true });
24
+
25
+ // Static methods
26
+ oddDebugSchema.static('findByBookmakerAndOdd', function (bookmakerId, oddId) {
27
+ return this.findOne({ bookmakerId, oddId });
28
+ });
29
+
30
+ const OddDebug = model('OddDebug', oddDebugSchema);
31
+
32
+ module.exports = { OddDebug };
@@ -0,0 +1,140 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const {
3
+ createBaseSchema,
4
+ baseSchemaOptions,
5
+ addAutoSlugHook,
6
+ addExternalRefsSlugHook
7
+ } = require('./base');
8
+
9
+ /**
10
+ * Player schema
11
+ */
12
+ const playerSchema = new Schema(
13
+ {
14
+ ...createBaseSchema(),
15
+ sportId: { type: String, required: true, ref: 'Sport' },
16
+ teamId: { type: String, required: true, ref: 'Team' },
17
+ resourceId: { type: String },
18
+ fullName: { type: String, required: true },
19
+ firstName: { type: String },
20
+ lastName: { type: String },
21
+ slug: { type: String, unique: true, sparse: true },
22
+ isActive: { type: Boolean, default: true },
23
+ metadata: { type: Schema.Types.Mixed, default: {} },
24
+ externalRefs: { type: Schema.Types.Mixed, default: {} }
25
+ },
26
+ baseSchemaOptions
27
+ );
28
+
29
+ // Indexes
30
+ playerSchema.index({ slug: 1 });
31
+ playerSchema.index({ sportId: 1 });
32
+ playerSchema.index({ isActive: 1 });
33
+ playerSchema.index({ fullName: 'text' });
34
+
35
+ // Virtuals
36
+ playerSchema.virtual('sport', {
37
+ ref: 'Sport',
38
+ localField: 'sportId',
39
+ foreignField: '_id',
40
+ justOne: true
41
+ });
42
+
43
+ playerSchema.virtual('team', {
44
+ ref: 'Team',
45
+ localField: 'teamId',
46
+ foreignField: '_id',
47
+ justOne: true
48
+ });
49
+
50
+ // Add auto-slug generation from fullName field
51
+ addAutoSlugHook(playerSchema, 'fullName');
52
+
53
+ // Static methods
54
+ playerSchema.static('findBySlug', function (slug) {
55
+ return this.findOne({ slug });
56
+ });
57
+
58
+ playerSchema.static('getActive', function () {
59
+ return this.find({ isActive: true }).sort({ name: 1 });
60
+ });
61
+
62
+ playerSchema.static('findByExternalRefId', function (bookmakerSlug, sportId, externalId) {
63
+ const query = {};
64
+ query[`externalRefs.${bookmakerSlug}.id`] = externalId;
65
+ query['sportId'] = sportId;
66
+ return this.findOne(query);
67
+ });
68
+
69
+ playerSchema.static('findBySport', function (sportId) {
70
+ return this.find({ sportId, isActive: true }).sort({ fullName: 1 });
71
+ });
72
+
73
+ playerSchema.static('findByTeam', function (teamId) {
74
+ return this.find({ teamId, isActive: true }).sort({ fullName: 1 });
75
+ });
76
+
77
+ playerSchema.static('searchByFullName', function (query) {
78
+ return this.find({
79
+ fullName: { $regex: query, $options: 'i' }
80
+ }).sort({ fullName: 1 });
81
+ });
82
+
83
+ // Instance methods
84
+ playerSchema.method('activate', async function () {
85
+ this.isActive = true;
86
+ return this.save();
87
+ });
88
+
89
+ playerSchema.method('deactivate', async function () {
90
+ this.isActive = false;
91
+ return this.save();
92
+ });
93
+
94
+ playerSchema.method('addExternalRef', async function (bookmakerSlug, externalId, data) {
95
+ const ref = {
96
+ id: externalId,
97
+ name: data.name || '',
98
+ slug: data.slug || '',
99
+ isActive: data.isActive !== undefined ? data.isActive : true,
100
+ metadata: data.metadata || {}
101
+ };
102
+ this.externalRefs[bookmakerSlug] = ref;
103
+ this.markModified('externalRefs');
104
+ return this.save();
105
+ });
106
+
107
+ playerSchema.method('getExternalRef', function (bookmakerSlug) {
108
+ return this.externalRefs?.[bookmakerSlug] || null;
109
+ });
110
+
111
+ playerSchema.method('removeExternalRef', async function (bookmakerSlug) {
112
+ if (this.externalRefs?.[bookmakerSlug]) {
113
+ delete this.externalRefs[bookmakerSlug];
114
+ this.markModified('externalRefs');
115
+ return this.save();
116
+ }
117
+ return this;
118
+ });
119
+
120
+ playerSchema.method('activateExternalRef', async function (bookmakerSlug) {
121
+ if (this.externalRefs?.[bookmakerSlug]) {
122
+ this.externalRefs[bookmakerSlug].isActive = true;
123
+ this.markModified('externalRefs');
124
+ return this.save();
125
+ }
126
+ return this;
127
+ });
128
+
129
+ playerSchema.method('deactivateExternalRef', async function (bookmakerSlug) {
130
+ if (this.externalRefs?.[bookmakerSlug]) {
131
+ this.externalRefs[bookmakerSlug].isActive = false;
132
+ this.markModified('externalRefs');
133
+ return this.save();
134
+ }
135
+ return this;
136
+ });
137
+
138
+ const Player = model('Player', playerSchema);
139
+
140
+ module.exports = { Player };