From 271396fc63f0253beca249afa0d90ab63dd44cdd Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Sun, 4 Jan 2026 07:08:05 -0600 Subject: [PATCH] Initial commit - betting-analytics-backend --- Dockerfile | 46 ++++++++++++++++++ package.json | 84 +++++++++++++++++++++++++++++++++ service.descriptor.yml | 54 +++++++++++++++++++++ src/app.module.ts | 32 +++++++++++++ src/config/index.ts | 23 +++++++++ src/main.ts | 36 ++++++++++++++ src/modules/auth/auth.module.ts | 19 ++++++++ src/shared/types/index.ts | 27 +++++++++++ tsconfig.json | 26 ++++++++++ 9 files changed, 347 insertions(+) create mode 100644 Dockerfile create mode 100644 package.json create mode 100644 service.descriptor.yml create mode 100644 src/app.module.ts create mode 100644 src/config/index.ts create mode 100644 src/main.ts create mode 100644 src/modules/auth/auth.module.ts create mode 100644 src/shared/types/index.ts create mode 100644 tsconfig.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3237dc7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# ============================================================================== +# BETTING-ANALYTICS BACKEND - Express.js Dockerfile +# ============================================================================== +# Multi-stage build for production-ready Express application +# ============================================================================== + +# Stage 1: Dependencies +FROM node:20-alpine AS deps +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --only=production && npm cache clean --force + +# Stage 2: Builder +FROM node:20-alpine AS builder +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Stage 3: Production +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 express + +# Copy built application +COPY --from=builder /app/dist ./dist +COPY --from=deps /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +USER express + +EXPOSE 3090 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3090/health || exit 1 + +CMD ["node", "dist/main.js"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..b182f1f --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "@betting-analytics/backend", + "version": "0.1.0", + "description": "Betting Analytics - Backend API", + "author": "Betting Analytics Team", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@nestjs/common": "^10.3.0", + "@nestjs/config": "^3.1.1", + "@nestjs/core": "^10.3.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", + "@nestjs/platform-express": "^10.3.0", + "@nestjs/typeorm": "^10.0.1", + "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "pg": "^8.11.3", + "reflect-metadata": "^0.2.1", + "rxjs": "^7.8.1", + "typeorm": "^0.3.19" + }, + "devDependencies": { + "@nestjs/cli": "^10.3.0", + "@nestjs/schematics": "^10.1.0", + "@nestjs/testing": "^10.3.0", + "@types/bcrypt": "^5.0.2", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.6", + "@types/passport-jwt": "^4.0.0", + "@types/passport-local": "^1.0.38", + "@typescript-eslint/eslint-plugin": "^6.18.0", + "@typescript-eslint/parser": "^6.18.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.2", + "jest": "^29.7.0", + "prettier": "^3.1.1", + "source-map-support": "^0.5.21", + "supertest": "^6.3.4", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.3.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/service.descriptor.yml b/service.descriptor.yml new file mode 100644 index 0000000..27ddb7b --- /dev/null +++ b/service.descriptor.yml @@ -0,0 +1,54 @@ +# ============================================================================== +# SERVICE DESCRIPTOR - BETTING ANALYTICS API +# ============================================================================== +version: "1.0.0" + +service: + name: "betting-api" + display_name: "Betting Analytics API" + description: "API para analisis de apuestas deportivas" + type: "backend" + runtime: "python" + framework: "fastapi" + owner_agent: "NEXUS-BACKEND" + +ports: + internal: 3050 + registry_ref: "projects.betting.services.api" + protocol: "http" + +database: + registry_ref: "betting" + role: "runtime" + +modules: + data_ingestion: + description: "Recoleccion de datos" + status: "planned" + analytics: + description: "Analisis estadistico" + status: "planned" + predictions: + description: "Modelos ML" + status: "planned" + +docker: + networks: + - "betting_${ENV:-local}" + - "infra_shared" + labels: + traefik: + enable: true + rule: "Host(`api.betting.localhost`)" + +healthcheck: + endpoint: "/health" + +status: + phase: "planned" + version: "0.0.1" + completeness: 5 + +metadata: + created_at: "2025-12-18" + project: "betting-analytics" diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..8d13052 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,32 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { appConfig, databaseConfig, jwtConfig } from './config'; +import { AuthModule } from './modules/auth/auth.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [appConfig, databaseConfig, jwtConfig], + }), + TypeOrmModule.forRootAsync({ + useFactory: (configService) => ({ + type: 'postgres', + host: configService.get('database.host'), + port: configService.get('database.port'), + username: configService.get('database.username'), + password: configService.get('database.password'), + database: configService.get('database.database'), + entities: [__dirname + '/**/*.entity{.ts,.js}'], + synchronize: configService.get('database.synchronize'), + logging: configService.get('database.logging'), + }), + inject: [ConfigModule], + }), + AuthModule, + ], + controllers: [], + providers: [], +}) +export class AppModule {} diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..a508fa9 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,23 @@ +import { registerAs } from '@nestjs/config'; + +export const databaseConfig = registerAs('database', () => ({ + type: 'postgres', + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT, 10) || 5432, + username: process.env.DB_USERNAME || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', + database: process.env.DB_NAME || 'betting_analytics', + synchronize: process.env.NODE_ENV !== 'production', + logging: process.env.NODE_ENV === 'development', +})); + +export const jwtConfig = registerAs('jwt', () => ({ + secret: process.env.JWT_SECRET || 'change-me-in-production', + expiresIn: process.env.JWT_EXPIRES_IN || '1d', +})); + +export const appConfig = registerAs('app', () => ({ + port: parseInt(process.env.PORT, 10) || 3000, + environment: process.env.NODE_ENV || 'development', + apiPrefix: process.env.API_PREFIX || 'api', +})); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..871564e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,36 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + const configService = app.get(ConfigService); + + // Global validation pipe + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + + // CORS configuration + app.enableCors({ + origin: process.env.CORS_ORIGIN || '*', + credentials: true, + }); + + // API prefix + const apiPrefix = configService.get('app.apiPrefix', 'api'); + app.setGlobalPrefix(apiPrefix); + + // Start server + const port = configService.get('app.port', 3000); + await app.listen(port); + + console.log(`Betting Analytics API running on: http://localhost:${port}/${apiPrefix}`); +} + +bootstrap(); diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..fe44ab6 --- /dev/null +++ b/src/modules/auth/auth.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; + +/** + * Authentication module placeholder + * + * TODO: Implement authentication logic including: + * - User authentication service + * - JWT strategy + * - Local strategy + * - Auth controller + * - Auth guards + */ +@Module({ + imports: [], + controllers: [], + providers: [], + exports: [], +}) +export class AuthModule {} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..f0da0ff --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,27 @@ +/** + * Shared type definitions for Betting Analytics + */ + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; +} + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + limit: number; + totalPages: number; +} + +export interface JwtPayload { + sub: string; + email: string; + iat?: number; + exp?: number; +} + +export type Environment = 'development' | 'production' | 'test'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c86586b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +}