Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5cb6803
Merge pull request #15 from dream-sports-labs/chore/MergeChanges
yashawasthi10 Apr 28, 2025
b6f0835
fix: added support for DOTA server in Delivr Prod account
mohithemnani11 Sep 24, 2025
f123699
fix: added trust policy
mohithemnani11 Sep 26, 2025
bf66128
fix: proxy fix
mohithemnani11 Sep 26, 2025
c64d61d
fix: removed rate limiting
mohithemnani11 Sep 26, 2025
577cddd
chore: fixed aws s3 connection issue
prathameshy7 Sep 30, 2025
54412fe
chore: added check for authorized email domains
prathameshy7 Oct 1, 2025
8dcba8b
feat: added apis and DB schema for user terms acceptance
prathameshy7 Oct 2, 2025
6f920ed
fix: fixed updatePackageHistory method for editing release
prathameshy7 Oct 8, 2025
f337374
Merge pull request #30 from dream-sports-labs/chore/external-google-l…
prathameshy7 Oct 13, 2025
be1a3d5
added pm2-uat.json
prathameshy7 Oct 13, 2025
3121f73
added isBundlePatchEnabled field in package
Racha2004 Oct 15, 2025
f7be689
added isBundlePatchingEnabled field in package
Racha2004 Oct 15, 2025
764d435
field added in utils.test.ts file
Racha2004 Oct 16, 2025
060fb95
memcached for updatecheck changes
Oct 15, 2025
de81b24
removed redis files dependency
Oct 15, 2025
a360089
chore: added pm22-stag file for stag deployment
prathameshy7 Oct 16, 2025
2f9bced
added code to containerize the whole application
Oct 16, 2025
8d89dd2
fixed compose file
Oct 16, 2025
708ff6c
Revert "chore: added pm22-stag file for stag deployment"
prathameshy7 Oct 17, 2025
17dbe54
Merge pull request #36 from dream-sports-labs/delivr/diffpatch
prathameshy7 Oct 17, 2025
8fb96ce
memcached dependency added
Oct 17, 2025
de11857
Merge branch 'delivr/MergeChanges' of github.com:dream-sports-labs/co…
mohithemnani11 Oct 17, 2025
f991775
fix: dependency of memcached locked at 2.2.2
mohithemnani11 Oct 17, 2025
80b88b0
Merge pull request #38 from ds-horizon/update_memcached_changes
prathameshy7 Oct 17, 2025
013c9ca
Merge pull request #41 from ds-horizon/feat/containerize
prathameshy7 Oct 17, 2025
6400c7b
added dev, prod setup
Oct 17, 2025
33bf880
reverted load and prod pm2 files
Oct 17, 2025
f9f2ef8
Merge pull request #42 from ds-horizon/feat/containerize
mohithemnani11 Oct 17, 2025
ef363f5
fix:
princektripathi Oct 17, 2025
eaf5299
Merge pull request #43 from ds-horizon/fix/docker-db-connection
navkashkrishna-d11 Oct 17, 2025
446556e
bug fixes in dota
Racha2004 Oct 28, 2025
91349e2
bugfix
Racha2004 Oct 28, 2025
660cf75
Mock Server
Racha2004 Oct 31, 2025
1d871c2
mockserver
Racha2004 Nov 3, 2025
ddd9bf7
mock server
Racha2004 Nov 4, 2025
8abe0ac
mock server
Racha2004 Nov 4, 2025
ebf2e60
mock server
Racha2004 Nov 4, 2025
49395df
mock server
Racha2004 Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM node:lts-alpine
WORKDIR /app
COPY api ./api
COPY pm2 ./pm2
WORKDIR /app/api
RUN npm install -g pm2
RUN npm install
EXPOSE 3010
12 changes: 12 additions & 0 deletions api/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,20 @@ To emulate Azure Blob Storage locally. Azurite needs to be installed and running
- `MICROSOFT_CLIENT_SECRET`
- `MICROSOFT_TENANT_ID`: Required if application registration is single tenant.

#### Google OAuth

- `GOOGLE_CLIENT_ID`: The client ID from Google Developer Console

## Optional parameters

### Authorization

- `LOGIN_AUTHORIZED_DOMAINS`: Comma-separated list of additional email domains authorized to access the service via Google OAuth.

### Terms and Conditions

- `CURRENT_TERMS_VERSION`: Version string for the current terms and conditions that users must accept. Example: `v1.0`, `v2.1`. Defaults to `v1.0` if not set.

### HTTPS
- `HTTPS`: Set to 'true' to enable HTTPS for local deployment

Expand Down
40 changes: 37 additions & 3 deletions api/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
version: '3.8'
services:
db:
image: mysql:5.7
platform: linux/amd64
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: codepushdb
MYSQL_USER: root
MYSQL_PASSWORD: root
ports:
- "3306:3306" # MySQL running on localhost:3306
volumes:
Expand All @@ -18,12 +15,49 @@ services:
ports:
- "6379:6379" # Redis running on localhost:6379

memcached:
image: memcached:alpine
ports:
- "11211:11211" # Memcached running on localhost:11211

localstack:
image: localstack/localstack
environment:
- SERVICES=s3
ports:
- "4566:4566" # LocalStack running on localhost:4566

app:
build:
context: ..
dockerfile: api/Dockerfile
ports:
- "3010:3010"
env_file:
- .env
volumes:
- ../api:/app/api
- ../pm2:/app/pm2
depends_on:
- redis
- db
- memcached
- localstack
working_dir: /app/api
command: >
/bin/sh -c "
sleep 10 &&
npm run build &&
if [ \"$$NODE_ENV\" = \"production\" ]; then
echo 'Starting in PRODUCTION mode...' &&
pm2-runtime start /app/pm2/pm2-prod.json;
else
echo 'Starting in DEVELOPMENT mode...' &&
echo 'Running database seeding...' &&
npx ts-node script/storage/seedData.ts &&
echo 'Starting PM2 with dev configuration...' &&
pm2-runtime start /app/pm2/pm2-dev.json;
fi
"
volumes:
db_data:
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"google-auth-library": "9.9.0",
"ioredis": "5.4.0",
"lusca": "^1.7.0",
"memcached": "2.2.2",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1",
"mysql2": "3.11.3",
Expand Down
24 changes: 15 additions & 9 deletions api/script/default-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { AzureStorage } from "./storage/azure-storage";
import { fileUploadMiddleware } from "./file-upload-manager";
import { JsonStorage } from "./storage/json-storage";
import { RedisManager } from "./redis-manager";
import { MemcachedManager } from "./memcached-manager";
import { Storage } from "./storage/storage";
import { Response } from "express";
import rateLimit from "express-rate-limit";
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || "<your-s3-bucket-name>";
const RDS_DB_INSTANCE_IDENTIFIER = process.env.RDS_DB_INSTANCE_IDENTIFIER || "<your-rds-instance>";
const SECRETS_MANAGER_SECRET_ID = process.env.SECRETS_MANAGER_SECRET_ID || "<your-secret-id>";
Expand Down Expand Up @@ -59,8 +59,14 @@ export function start(done: (err?: any, server?: express.Express, storage?: Stor
})
.then(() => {
const app = express();
// Trust a specific number of proxy hops (safer than boolean true).
// Configure via TRUST_PROXY_HOPS; default to 1 when sitting behind a single proxy/ELB.
const trustProxyHops = parseInt(process.env.TRUST_PROXY_HOPS || "1", 10);
app.set("trust proxy", trustProxyHops);
console.log(`Trust proxy hops: ${trustProxyHops}`);
const auth = api.auth({ storage: storage });
const redisManager = new RedisManager();
const memcachedManager = new MemcachedManager();

// First, to wrap all requests and catch all exceptions.
app.use(domain);
Expand Down Expand Up @@ -135,16 +141,12 @@ export function start(done: (err?: any, server?: express.Express, storage?: Stor
app.set("view engine", "ejs");
app.use("/auth/images/", express.static(__dirname + "/views/images"));
app.use(api.headers({ origin: process.env.CORS_ORIGIN || "http://localhost:4000" }));
app.use(api.health({ storage: storage, redisManager: redisManager }));
app.use(api.health({ storage: storage, redisManager: redisManager, memcachedManager: memcachedManager }));

const limiter = rateLimit({
windowMs: 1000, // 1 minute
max: 2000, // limit each IP to 100 requests per windowMs
validate: { xForwardedForHeader: false }
});
// Rate limiting removed: relying on CloudFront + WAF for request throttling

if (process.env.DISABLE_ACQUISITION !== "true") {
app.use(api.acquisition({ storage: storage, redisManager: redisManager }));
app.use(api.acquisition({ storage: storage, redisManager: redisManager, memcachedManager: memcachedManager }));
}

if (process.env.DISABLE_MANAGEMENT !== "true") {
Expand All @@ -166,11 +168,15 @@ export function start(done: (err?: any, server?: express.Express, storage?: Stor
} else {
app.use(auth.router());
}
app.use(auth.authenticate, fileUploadMiddleware, limiter, api.management({ storage: storage, redisManager: redisManager }));
app.use(auth.authenticate, fileUploadMiddleware, api.management({ storage: storage, redisManager: redisManager }));
} else {
app.use(auth.router());
}

done(null, app, storage);
})
.catch((error) => {
console.error("Error starting server:", error);
done(error);
});
}
127 changes: 127 additions & 0 deletions api/script/memcached-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import * as crypto from "crypto";

// Using memcached library for Node.js
const Memcached = require('memcached');

export interface CacheableResponse {
statusCode: number;
body: any;
}

/**
* Minimal Memcached Manager for updateCheck API caching only
* Replaces Redis for updateCheck API responses
*/
export class MemcachedManager {
private client: any;
private setupPromise: Promise<void>;
private keyPrefix: string;

constructor() {
const memcachedServers = process.env.MEMCACHED_SERVERS || 'localhost:11211';
this.keyPrefix = process.env.MEMCACHED_KEY_PREFIX || 'codepush:';

// Initialize Memcached client
this.client = new Memcached(memcachedServers, {
timeout: parseInt(process.env.MEMCACHED_TIMEOUT || '5000'),
retries: parseInt(process.env.MEMCACHED_RETRIES || '3'),
retry: parseInt(process.env.MEMCACHED_RETRY_DELAY || '1000'),
failures: parseInt(process.env.MEMCACHED_MAX_FAILURES || '5'),
keyCompression: false,
maxKeySize: 250, // Memcached limit
maxValue: 1048576 // 1MB limit
});

this.setupPromise = this.setup();
}

private async setup(): Promise<void> {
return new Promise((resolve, reject) => {
// Test connection
this.client.version((err: any, version: any) => {
if (err) {
console.warn('Memcached connection failed, will operate without cache:', err.message);
resolve(); // Don't fail, just operate without cache
} else {
console.log('✅ Memcached connected successfully, version:', version);
resolve();
}
});
});
}

/**
* Health check for Memcached connectivity
*/
public async checkHealth(): Promise<void> {
return this.setupPromise;
}

/**
* Get cached response for updateCheck API
*/
public async getCachedResponse(deploymentKey: string, urlKey: string): Promise<CacheableResponse | null> {
await this.setupPromise;

return new Promise((resolve) => {
const key = this.createKey('cache', this.getDeploymentKeyHash(deploymentKey), this.hashString(urlKey, 16));

this.client.get(key, (err: any, data: any) => {
if (err || !data) {
resolve(null);
return;
}

try {
resolve(JSON.parse(data));
} catch (parseErr) {
console.error('Failed to parse cached response:', parseErr);
resolve(null);
}
});
});
}

/**
* Set cached response for updateCheck API
*/
public async setCachedResponse(deploymentKey: string, urlKey: string, response: CacheableResponse, ttlSeconds?: number): Promise<void> {
await this.setupPromise;

const ttl = ttlSeconds || parseInt(process.env.CACHE_TTL_SECONDS || '3600'); // 1 hour default
const key = this.createKey('cache', this.getDeploymentKeyHash(deploymentKey), this.hashString(urlKey, 16));
const data = JSON.stringify(response);

return new Promise((resolve) => {
this.client.set(key, data, ttl, (err: any) => {
if (err) {
console.error('Failed to set cache:', err);
}
resolve(); // Don't fail the request if cache set fails
});
});
}

// ===== UTILITY METHODS =====

private createKey(...parts: string[]): string {
return this.keyPrefix + parts.filter(p => p).join(':');
}

/**
* Get deployment key hash for consistent key generation
*/
private getDeploymentKeyHash(deploymentKey: string): string {
return crypto.createHash('sha256').update(deploymentKey).digest('hex').substring(0, 16);
}

/**
* Hash any string to specified length
*/
private hashString(input: string, length: number = 8): string {
return crypto.createHash('sha256').update(input).digest('hex').substring(0, length);
}
}
Loading