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,145 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const {
3
+ createBaseSchema,
4
+ baseSchemaOptions,
5
+ addAutoSlugHook,
6
+ addExternalRefsSlugHook
7
+ } = require('./base');
8
+
9
+ /**
10
+ * Sport schema
11
+ */
12
+ const sportSchema = new Schema(
13
+ {
14
+ ...createBaseSchema(),
15
+ name: { type: String, required: true },
16
+ slug: { type: String, required: true },
17
+ displayName: { type: String, required: true },
18
+ isActive: { type: Boolean, default: true },
19
+ externalRefs: { type: Schema.Types.Mixed, default: {} }
20
+ },
21
+ {
22
+ ...baseSchemaOptions,
23
+ autoIndex: process.env.NODE_ENV !== 'production'
24
+ }
25
+ );
26
+
27
+ // Indexes
28
+ sportSchema.index({ slug: 1 }, { unique: true, sparse: true });
29
+ sportSchema.index({ isActive: 1 });
30
+ sportSchema.index({ name: 'text', displayName: 'text' });
31
+
32
+ // Clear any duplicate indexes
33
+ if (sportSchema.clearIndexes) {
34
+ sportSchema.clearIndexes();
35
+ }
36
+
37
+ // Migration hook: convert array externalRefs to object
38
+ sportSchema.pre('save', function (next) {
39
+ if (this.externalRefs && Array.isArray(this.externalRefs)) {
40
+ // Migrate from array to object format
41
+ const converted = {};
42
+ this.externalRefs.forEach((ref) => {
43
+ if (ref && ref.bookmaker) {
44
+ // If array has bookmaker property, use it as key
45
+ const { bookmaker, ...refData } = ref;
46
+ converted[bookmaker] = refData;
47
+ } else if (ref && ref.slug) {
48
+ // Fallback to slug as key
49
+ converted[ref.slug] = ref;
50
+ }
51
+ });
52
+ this.externalRefs = converted;
53
+ }
54
+ next();
55
+ });
56
+
57
+ // Add auto-slug generation from name field
58
+ addAutoSlugHook(sportSchema, 'name');
59
+
60
+ // Add auto-slug generation for external refs
61
+ addExternalRefsSlugHook(sportSchema, 'externalRefs');
62
+
63
+ // Static methods
64
+ sportSchema.static('findBySlug', function (slug) {
65
+ return this.findOne({ slug });
66
+ });
67
+
68
+ sportSchema.static('getActive', function () {
69
+ return this.find({ isActive: true }).sort({ name: 1 });
70
+ });
71
+
72
+ sportSchema.static('findByExternalRefId', function (refId) {
73
+ return this.find({ 'externalRefs.id': refId });
74
+ });
75
+
76
+ sportSchema.static('searchByName', function (query) {
77
+ return this.find({
78
+ $or: [
79
+ { name: { $regex: query, $options: 'i' } },
80
+ { displayName: { $regex: query, $options: 'i' } }
81
+ ]
82
+ }).sort({ name: 1 });
83
+ });
84
+
85
+ // Instance methods
86
+ sportSchema.method('activate', async function () {
87
+ this.isActive = true;
88
+ return this.save();
89
+ });
90
+
91
+ sportSchema.method('deactivate', async function () {
92
+ this.isActive = false;
93
+ return this.save();
94
+ });
95
+
96
+ sportSchema.method('addExternalRef', async function (key, ref) {
97
+ if (!this.externalRefs || typeof this.externalRefs !== 'object') {
98
+ this.externalRefs = {};
99
+ }
100
+ const refs = this.externalRefs;
101
+ refs[key] = ref;
102
+ this.markModified('externalRefs');
103
+ return this.save();
104
+ });
105
+
106
+ sportSchema.method('removeExternalRef', async function (key) {
107
+ if (this.externalRefs && typeof this.externalRefs === 'object') {
108
+ const refs = this.externalRefs;
109
+ delete refs[key];
110
+ this.markModified('externalRefs');
111
+ }
112
+ return this.save();
113
+ });
114
+
115
+ sportSchema.method('activateExternalRef', async function (key) {
116
+ if (this.externalRefs && typeof this.externalRefs === 'object') {
117
+ const refs = this.externalRefs;
118
+ const ref = refs[key];
119
+ if (ref) {
120
+ ref.isActive = true;
121
+ refs[key] = ref;
122
+ this.markModified('externalRefs');
123
+ return this.save();
124
+ }
125
+ }
126
+ return this;
127
+ });
128
+
129
+ sportSchema.method('deactivateExternalRef', async function (key) {
130
+ if (this.externalRefs && typeof this.externalRefs === 'object') {
131
+ const refs = this.externalRefs;
132
+ const ref = refs[key];
133
+ if (ref) {
134
+ ref.isActive = false;
135
+ refs[key] = ref;
136
+ this.markModified('externalRefs');
137
+ return this.save();
138
+ }
139
+ }
140
+ return this;
141
+ });
142
+
143
+ const Sport = model('Sport', sportSchema);
144
+
145
+ module.exports = { Sport };
package/models/team.js ADDED
@@ -0,0 +1,133 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const {
3
+ createBaseSchema,
4
+ baseSchemaOptions,
5
+ addAutoSlugHook,
6
+ addExternalRefsSlugHook
7
+ } = require('./base');
8
+
9
+ /**
10
+ * Team schema
11
+ */
12
+ const teamSchema = new Schema(
13
+ {
14
+ ...createBaseSchema(),
15
+ sportId: { type: String, required: true, ref: 'Sport' },
16
+ resourceId: { type: String },
17
+ name: { type: String, required: true },
18
+ slug: { type: String, unique: true, sparse: true },
19
+ isActive: { type: Boolean, default: true },
20
+ metadata: { type: Schema.Types.Mixed, default: {} },
21
+ externalRefs: { type: Schema.Types.Mixed, default: {} }
22
+ },
23
+ baseSchemaOptions
24
+ );
25
+
26
+ // Indexes
27
+ teamSchema.index({ slug: 1 });
28
+ teamSchema.index({ sportId: 1 });
29
+ teamSchema.index({ isActive: 1 });
30
+ teamSchema.index({ name: 'text' });
31
+
32
+ // Virtuals
33
+ teamSchema.virtual('sport', {
34
+ ref: 'Sport',
35
+ localField: 'sportId',
36
+ foreignField: '_id',
37
+ justOne: true
38
+ });
39
+
40
+ // Add auto-slug generation from name field
41
+ addAutoSlugHook(teamSchema, 'name');
42
+
43
+ // Static methods
44
+ teamSchema.static('findBySlug', function (slug) {
45
+ return this.findOne({ slug });
46
+ });
47
+
48
+ teamSchema.static('getActive', function () {
49
+ return this.find({ isActive: true }).sort({ name: 1 });
50
+ });
51
+
52
+ teamSchema.static('findByExternalRef', function (bookmakerSlug, sportId, externalId) {
53
+ const query = {};
54
+ query[`externalRefs.${bookmakerSlug}.id`] = externalId;
55
+ query['sportId'] = sportId;
56
+ return this.findOne(query);
57
+ });
58
+
59
+ teamSchema.static('findByExternalRefName', function (bookmakerSlug, sportId, externalName) {
60
+ const query = {};
61
+ query[`externalRefs.${bookmakerSlug}.name`] = externalName;
62
+ query['sportId'] = sportId;
63
+ return this.findOne(query);
64
+ });
65
+
66
+ teamSchema.static('findBySport', function (sportId) {
67
+ return this.find({ sportId, isActive: true }).sort({ name: 1 });
68
+ });
69
+
70
+ teamSchema.static('searchByName', function (query) {
71
+ return this.find({
72
+ name: { $regex: query, $options: 'i' }
73
+ }).sort({ name: 1 });
74
+ });
75
+
76
+ // Instance methods
77
+ teamSchema.method('activate', async function () {
78
+ this.isActive = true;
79
+ return this.save();
80
+ });
81
+
82
+ teamSchema.method('deactivate', async function () {
83
+ this.isActive = false;
84
+ return this.save();
85
+ });
86
+
87
+ teamSchema.method('addExternalRef', async function (bookmakerSlug, externalId, data) {
88
+ const ref = {
89
+ id: externalId,
90
+ name: data.name,
91
+ slug: data.slug,
92
+ isActive: data.isActive !== undefined ? data.isActive : true,
93
+ metadata: data.metadata || {}
94
+ };
95
+ this.externalRefs[bookmakerSlug] = ref;
96
+ this.markModified('externalRefs');
97
+ return this.save();
98
+ });
99
+
100
+ teamSchema.method('getExternalRef', function (bookmakerSlug) {
101
+ return this.externalRefs?.[bookmakerSlug] || null;
102
+ });
103
+
104
+ teamSchema.method('removeExternalRef', async function (bookmakerSlug) {
105
+ if (this.externalRefs?.[bookmakerSlug]) {
106
+ delete this.externalRefs[bookmakerSlug];
107
+ this.markModified('externalRefs');
108
+ return this.save();
109
+ }
110
+ return this;
111
+ });
112
+
113
+ teamSchema.method('activateExternalRef', async function (bookmakerSlug) {
114
+ if (this.externalRefs?.[bookmakerSlug]) {
115
+ this.externalRefs[bookmakerSlug].isActive = true;
116
+ this.markModified('externalRefs');
117
+ return this.save();
118
+ }
119
+ return this;
120
+ });
121
+
122
+ teamSchema.method('deactivateExternalRef', async function (bookmakerSlug) {
123
+ if (this.externalRefs?.[bookmakerSlug]) {
124
+ this.externalRefs[bookmakerSlug].isActive = false;
125
+ this.markModified('externalRefs');
126
+ return this.save();
127
+ }
128
+ return this;
129
+ });
130
+
131
+ const Team = model('Team', teamSchema);
132
+
133
+ module.exports = { Team };
package/models/user.js ADDED
@@ -0,0 +1,62 @@
1
+ const { Schema, model } = require('mongoose');
2
+ const { createBaseSchema, baseSchemaOptions } = require('./base');
3
+
4
+ /**
5
+ * User schema
6
+ */
7
+ const userSchema = new Schema(
8
+ {
9
+ ...createBaseSchema(),
10
+ email: { type: String, required: true, unique: true, lowercase: true, trim: true },
11
+ password: { type: String, required: true },
12
+ name: { type: String, required: true },
13
+ role: { type: String, enum: ['admin', 'user'], default: 'user' },
14
+ isActive: { type: Boolean, default: true }
15
+ },
16
+ baseSchemaOptions
17
+ );
18
+
19
+ // Indexes
20
+ userSchema.index({ isActive: 1 });
21
+ userSchema.index({ role: 1 });
22
+
23
+ // Static methods
24
+ userSchema.static('findByEmail', function (email) {
25
+ return this.findOne({ email: email.toLowerCase() });
26
+ });
27
+
28
+ userSchema.static('getActive', function () {
29
+ return this.find({ isActive: true }).select('-password');
30
+ });
31
+
32
+ userSchema.static('findAdmins', function () {
33
+ return this.find({ role: 'admin', isActive: true }).select('-password');
34
+ });
35
+
36
+ // Instance methods
37
+ userSchema.method('activate', async function () {
38
+ this.isActive = true;
39
+ return this.save();
40
+ });
41
+
42
+ userSchema.method('deactivate', async function () {
43
+ this.isActive = false;
44
+ return this.save();
45
+ });
46
+
47
+ userSchema.method('comparePassword', async function (password) {
48
+ // For now, simple comparison. In production, use bcrypt
49
+ return this.password === password;
50
+ });
51
+
52
+ // Middleware to exclude password from toJSON
53
+ userSchema.set('toJSON', {
54
+ transform: (doc, ret) => {
55
+ delete ret.password;
56
+ return ret;
57
+ }
58
+ });
59
+
60
+ const User = model('User', userSchema);
61
+
62
+ module.exports = { User };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "crazy-odds-bet-shared",
3
+ "version": "1.0.0",
4
+ "description": "Shared MongoDB models and utilities for odds project",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "db",
9
+ "models",
10
+ "utils"
11
+ ],
12
+ "scripts": {
13
+ "lint": "echo \"Lint not configured yet\"",
14
+ "test": "echo \"No tests yet\""
15
+ },
16
+ "keywords": [
17
+ "mongodb",
18
+ "models",
19
+ "utilities",
20
+ "shared"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "dotenv": "^16.3.1",
26
+ "mongoose": "^8.0.0",
27
+ "uuid": "^9.0.1"
28
+ }
29
+ }
package/utils/index.js ADDED
@@ -0,0 +1,4 @@
1
+ const { logger } = require('./logging/logger.js');
2
+ const { getLoggerConfig } = require('./logging/config.js');
3
+
4
+ module.exports = { logger, getLoggerConfig };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Unified logging configuration
3
+ * Simple JSON logging for production, pretty-printed in development
4
+ */
5
+
6
+ const getLoggerConfig = (nodeEnv = 'development') => {
7
+ const isDev = nodeEnv !== 'production';
8
+ const logLevel = process.env.LOG_LEVEL || (isDev ? 'debug' : 'info');
9
+
10
+ return {
11
+ isDev,
12
+ logLevel,
13
+ colorize: isDev,
14
+ serializers: {
15
+ req: (req) => ({
16
+ method: req.method,
17
+ url: req.url,
18
+ headers: {
19
+ host: req.headers?.host,
20
+ 'content-type': req.headers?.['content-type'],
21
+ 'user-agent': req.headers?.['user-agent']
22
+ },
23
+ remoteAddress: req.ip,
24
+ remotePort: req.socket?.remotePort
25
+ }),
26
+ res: (res) => ({
27
+ statusCode: res.statusCode,
28
+ responseTime: res.responseTime,
29
+ headers: res.getHeaders?.()
30
+ }),
31
+ error: (err) => ({
32
+ message: err.message,
33
+ stack: err.stack,
34
+ code: err.code,
35
+ statusCode: err.statusCode
36
+ })
37
+ }
38
+ };
39
+ };
40
+
41
+ module.exports = { getLoggerConfig };
@@ -0,0 +1,55 @@
1
+ const { getLoggerConfig } = require('./config');
2
+
3
+ const nodeEnv = process.env.NODE_ENV || 'development';
4
+ const config = getLoggerConfig(nodeEnv);
5
+
6
+ // Simple logger without external dependencies
7
+ const logger = {
8
+ debug: (message, data) => logMessage('DEBUG', message, data),
9
+ info: (message, data) => logMessage('INFO', message, data),
10
+ warn: (message, data) => logMessage('WARN', message, data),
11
+ error: (message, data) => logMessage('ERROR', message, data),
12
+ fatal: (message, data) => logMessage('FATAL', message, data)
13
+ };
14
+
15
+ function logMessage(level, message, data) {
16
+ // Check if log level should be displayed
17
+ const logLevels = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 };
18
+ const currentLevel = logLevels[config.logLevel] || 1;
19
+ const messageLevel = logLevels[level.toLowerCase()] || 0;
20
+
21
+ if (messageLevel < currentLevel) return;
22
+
23
+ const timestamp = new Date().toISOString();
24
+ const logEntry = {
25
+ timestamp,
26
+ level,
27
+ message,
28
+ ...(data && typeof data === 'object' ? data : {})
29
+ };
30
+
31
+ if (config.isDev) {
32
+ // Pretty print for development
33
+ const color = getColorCode(level);
34
+ console.log(
35
+ `${color}[${timestamp}] ${level}${'\x1b[0m'} ${message}`,
36
+ data && Object.keys(data).length > 0 ? JSON.stringify(data, null, 2) : ''
37
+ );
38
+ } else {
39
+ // JSON output for production
40
+ console.log(JSON.stringify(logEntry));
41
+ }
42
+ }
43
+
44
+ function getColorCode(level) {
45
+ const colors = {
46
+ DEBUG: '\x1b[36m', // cyan
47
+ INFO: '\x1b[32m', // green
48
+ WARN: '\x1b[33m', // yellow
49
+ ERROR: '\x1b[31m', // red
50
+ FATAL: '\x1b[35m' // magenta
51
+ };
52
+ return colors[level] || '';
53
+ }
54
+
55
+ module.exports = { logger };