Initial commit - betting-analytics-backend

This commit is contained in:
rckrdmrd 2026-01-04 07:08:05 -06:00
commit 271396fc63
9 changed files with 347 additions and 0 deletions

46
Dockerfile Normal file
View File

@ -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"]

84
package.json Normal file
View File

@ -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"
}
}

54
service.descriptor.yml Normal file
View File

@ -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"

32
src/app.module.ts Normal file
View File

@ -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 {}

23
src/config/index.ts Normal file
View File

@ -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',
}));

36
src/main.ts Normal file
View File

@ -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<string>('app.apiPrefix', 'api');
app.setGlobalPrefix(apiPrefix);
// Start server
const port = configService.get<number>('app.port', 3000);
await app.listen(port);
console.log(`Betting Analytics API running on: http://localhost:${port}/${apiPrefix}`);
}
bootstrap();

View File

@ -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 {}

27
src/shared/types/index.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* Shared type definitions for Betting Analytics
*/
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
export interface PaginatedResponse<T> {
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';

26
tsconfig.json Normal file
View File

@ -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"]
}