updates for testmode

This commit is contained in:
Emily
2025-01-29 17:14:10 +01:00
parent bfeee8673c
commit a2e4ed9ee0
22 changed files with 343 additions and 61 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ dev
docker-compose.admin.yml docker-compose.admin.yml
full_reload.sh full_reload.sh
tmp tmp
ecosystem.config.js

View File

@@ -24,6 +24,7 @@ winston-*.ndjson
.env.* .env.*
!.env.example !.env.example
ecosystem.config.js
# Test reports # Test reports
*.report.txt *.report.txt

View File

@@ -1,7 +1,7 @@
module.exports = { module.exports = {
apps: [ apps: [
{ {
name: 'Dashboard', name: 'dashboard',
port: '3010', port: '3010',
exec_mode: 'fork', exec_mode: 'fork',
script: './.output/server/index.mjs', script: './.output/server/index.mjs',

View File

@@ -13,7 +13,7 @@
"docker-inspect": "docker run -it litlyx-dashboard sh", "docker-inspect": "docker run -it litlyx-dashboard sh",
"docker-run": "docker run -p 3000:3000 litlyx-dashboard", "docker-run": "docker run -p 3000:3000 litlyx-dashboard",
"workspace:shared": "ts-node ../scripts/dashboard/shared.ts", "workspace:shared": "ts-node ../scripts/dashboard/shared.ts",
"workspace:deploy": "ts-node ../scripts/dashboard/deploy.ts --testmode" "workspace:deploy": "ts-node ../scripts/dashboard/deploy.ts"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/tailwindcss": "^6.12.0", "@nuxtjs/tailwindcss": "^6.12.0",

View File

@@ -1,4 +1,3 @@
export const PURCHASE_EMAIL = `<!DOCTYPE html> export const PURCHASE_EMAIL = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -21,15 +20,10 @@ export const PURCHASE_EMAIL = `<!DOCTYPE html>
<p>If you have any questions about your new plan or need assistance, feel free to reach out to our support team at <a href="mailto:help@litlyx.com" style="color: #28a745; text-decoration: none;"><strong>help@litlyx.com</strong></a>. Were here to help you make the most out of your upgraded plan!</p> <p>If you have any questions about your new plan or need assistance, feel free to reach out to our support team at <a href="mailto:help@litlyx.com" style="color: #28a745; text-decoration: none;"><strong>help@litlyx.com</strong></a>. Were here to help you make the most out of your upgraded plan!</p>
<p><strong>Thank you for using Litlyx every day as your analytics tool and for being a part of our journey.</strong></p>
<p>We look forward to continuing to support your growth and success!</p>
<p>Best regards,</p> <p>Best regards,</p>
<p>Antonio,</p> <p>Antonio,</p>
<p>CEO | Litlyx</p> <p>CEO @ Litlyx</p>
</body> </body>
</html> </html>`
`

View File

@@ -7,7 +7,10 @@
"dashboard:clear-logs": "ts-node scripts/dashboard/clear-logs.ts", "dashboard:clear-logs": "ts-node scripts/dashboard/clear-logs.ts",
"dashboard:shared": "ts-node scripts/dashboard/shared.ts", "dashboard:shared": "ts-node scripts/dashboard/shared.ts",
"dashboard:deploy": "ts-node scripts/dashboard/deploy.ts", "dashboard:deploy": "ts-node scripts/dashboard/deploy.ts",
"producer:shared": "node scripts/producer/shared.js",
"producer:shared": "ts-node scripts/producer/shared.ts",
"producer:deploy": "ts-node scripts/producer/deploy.ts",
"email:deploy": "ts-node scripts/email/deploy.ts" "email:deploy": "ts-node scripts/email/deploy.ts"
}, },
"keywords": [], "keywords": [],

1
producer/.gitignore vendored
View File

@@ -1,6 +1,7 @@
node_modules node_modules
static static
ecosystem.config.cjs ecosystem.config.cjs
ecosystem.config.js
dist dist
start_dev.js start_dev.js
package-lock.json package-lock.json

View File

@@ -1,7 +1,8 @@
{ {
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.19.2" "express": "^4.19.2",
"redis": "^4.7.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
@@ -19,7 +20,9 @@
"build_project": "node ../scripts/build.js", "build_project": "node ../scripts/build.js",
"build": "npm run compile && npm run build_project", "build": "npm run compile && npm run build_project",
"docker-build": "docker build -t litlyx-producer -f Dockerfile ../", "docker-build": "docker build -t litlyx-producer -f Dockerfile ../",
"docker-inspect": "docker run -it litlyx-producer sh" "docker-inspect": "docker run -it litlyx-producer sh",
"workspace:shared": "ts-node ../scripts/producer/shared.ts",
"workspace:deploy": "ts-node ../scripts/producer/deploy.ts"
}, },
"keywords": [], "keywords": [],
"author": "Emily", "author": "Emily",

View File

@@ -14,6 +14,9 @@ importers:
express: express:
specifier: ^4.19.2 specifier: ^4.19.2
version: 4.19.2 version: 4.19.2
redis:
specifier: ^4.7.0
version: 4.7.0
devDependencies: devDependencies:
'@types/cors': '@types/cors':
specifier: ^2.8.17 specifier: ^2.8.17
@@ -47,6 +50,35 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@redis/bloom@1.2.0':
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
peerDependencies:
'@redis/client': ^1.0.0
'@redis/client@1.6.0':
resolution: {integrity: sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==}
engines: {node: '>=14'}
'@redis/graph@1.1.1':
resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
peerDependencies:
'@redis/client': ^1.0.0
'@redis/json@1.0.7':
resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
peerDependencies:
'@redis/client': ^1.0.0
'@redis/search@1.2.0':
resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
peerDependencies:
'@redis/client': ^1.0.0
'@redis/time-series@1.1.0':
resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
peerDependencies:
'@redis/client': ^1.0.0
'@tsconfig/node10@1.0.11': '@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -126,6 +158,10 @@ packages:
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
content-disposition@0.5.4: content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -213,6 +249,10 @@ packages:
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
generic-pool@3.9.0:
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
engines: {node: '>= 4'}
get-intrinsic@1.2.4: get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -321,6 +361,9 @@ packages:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
redis@4.7.0:
resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==}
safe-buffer@5.2.1: safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -395,6 +438,9 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yn@3.1.1: yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -414,6 +460,32 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
'@redis/bloom@1.2.0(@redis/client@1.6.0)':
dependencies:
'@redis/client': 1.6.0
'@redis/client@1.6.0':
dependencies:
cluster-key-slot: 1.1.2
generic-pool: 3.9.0
yallist: 4.0.0
'@redis/graph@1.1.1(@redis/client@1.6.0)':
dependencies:
'@redis/client': 1.6.0
'@redis/json@1.0.7(@redis/client@1.6.0)':
dependencies:
'@redis/client': 1.6.0
'@redis/search@1.2.0(@redis/client@1.6.0)':
dependencies:
'@redis/client': 1.6.0
'@redis/time-series@1.1.0(@redis/client@1.6.0)':
dependencies:
'@redis/client': 1.6.0
'@tsconfig/node10@1.0.11': {} '@tsconfig/node10@1.0.11': {}
'@tsconfig/node12@1.0.11': {} '@tsconfig/node12@1.0.11': {}
@@ -514,6 +586,8 @@ snapshots:
get-intrinsic: 1.2.4 get-intrinsic: 1.2.4
set-function-length: 1.2.2 set-function-length: 1.2.2
cluster-key-slot@1.1.2: {}
content-disposition@0.5.4: content-disposition@0.5.4:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@@ -615,6 +689,8 @@ snapshots:
function-bind@1.1.2: {} function-bind@1.1.2: {}
generic-pool@3.9.0: {}
get-intrinsic@1.2.4: get-intrinsic@1.2.4:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -707,6 +783,15 @@ snapshots:
iconv-lite: 0.4.24 iconv-lite: 0.4.24
unpipe: 1.0.0 unpipe: 1.0.0
redis@4.7.0:
dependencies:
'@redis/bloom': 1.2.0(@redis/client@1.6.0)
'@redis/client': 1.6.0
'@redis/graph': 1.1.1(@redis/client@1.6.0)
'@redis/json': 1.0.7(@redis/client@1.6.0)
'@redis/search': 1.2.0(@redis/client@1.6.0)
'@redis/time-series': 1.1.0(@redis/client@1.6.0)
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {} safer-buffer@2.1.2: {}
@@ -795,4 +880,6 @@ snapshots:
vary@1.1.2: {} vary@1.1.2: {}
yallist@4.0.0: {}
yn@3.1.1: {} yn@3.1.1: {}

View File

@@ -1,7 +1,7 @@
import { Router, json } from "express"; import { Router, json } from "express";
import { createSessionHash, getIPFromRequest } from "./utils"; import { createSessionHash, getIPFromRequest } from "./utils";
import { requireEnv } from "@utils/requireEnv"; import { requireEnv } from "./shared/utils/requireEnv";
import { RedisStreamService } from "@services/RedisStreamService"; import { RedisStreamService } from "./shared/services/RedisStreamService";
const router = Router(); const router = Router();

View File

@@ -1,5 +1,5 @@
import { requireEnv } from "@utils/requireEnv"; import { requireEnv } from "./shared/utils/requireEnv";
import { RedisStreamService } from "@services/RedisStreamService"; import { RedisStreamService } from "./shared/services/RedisStreamService";
import express from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
@@ -23,7 +23,7 @@ app.post('/event', express.json(jsonOptions), async (req, res) => {
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent); const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
await RedisStreamService.addToStream(streamName, { await RedisStreamService.addToStream(streamName, {
...req.body, _type: 'event', sessionHash, ip, flowHash ...req.body, _type: 'event', sessionHash, ip, flowHash
}); });
return res.sendStatus(200); return res.sendStatus(200);
} catch (ex: any) { } catch (ex: any) {
return res.status(500).json({ error: ex.message }); return res.status(500).json({ error: ex.message });
@@ -59,8 +59,9 @@ app.post('/keep_alive', express.json(jsonOptions), async (req, res) => {
}); });
async function main() { async function main() {
const PORT = requireEnv("PORT");
await RedisStreamService.connect(); await RedisStreamService.connect();
app.listen(requireEnv("PORT"), () => console.log(`Listening on port ${requireEnv("PORT")}`)); app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
} }
main(); main();

View File

@@ -0,0 +1,90 @@
import { createClient } from 'redis';
import { requireEnv } from '../utils/requireEnv';
export type ReadingLoopOptions = {
stream_name: string,
group_name: ConsumerGroup,
consumer_name: string
}
type xReadGroupMessage = { id: string, message: { [x: string]: string } }
type xReadGgroupResult = { name: string, messages: xReadGroupMessage[] }[] | null
const consumerGroups = ['DATABASE'] as const;
type ConsumerGroup = typeof consumerGroups[number];
export class RedisStreamService {
private static client = createClient({
url: requireEnv("REDIS_URL"),
username: requireEnv("REDIS_USERNAME"),
password: requireEnv("REDIS_PASSWORD"),
database: process.env.DEV_MODE === 'true' ? 1 : 0
});
static async connect() {
await this.client.connect();
}
static async readFromStream(stream_name: string, group_name: string, consumer_name: string, process_function: (content: Record<string, string>) => Promise<any>) {
const result: xReadGgroupResult = await this.client.xReadGroup(group_name, consumer_name, [{ key: stream_name, id: '>' }], { COUNT: 5, BLOCK: 10000 });
if (!result) {
setTimeout(() => this.readFromStream(stream_name, group_name, consumer_name, process_function), 10);
return;
}
for (const entry of result) {
for (const messageData of entry.messages) {
await process_function(messageData.message);
await this.client.xAck(stream_name, group_name, messageData.id);
await this.client.set(`ACK:${group_name}`, messageData.id);
}
}
await this.trimStream(stream_name);
setTimeout(() => this.readFromStream(stream_name, group_name, consumer_name, process_function), 10);
return;
}
private static async trimStream(stream_name: string) {
let lastMessageAck = '0';
for (const consumerGroup of consumerGroups) {
const lastAck = await this.client.get(`ACK:${consumerGroup}`);
if (!lastAck) continue;
if (lastAck > lastMessageAck) lastMessageAck = lastAck;
}
await this.client.xTrim(stream_name, 'MINID', lastMessageAck as any);
}
static async startReadingLoop(options: ReadingLoopOptions, processFunction: (content: Record<string, string>) => Promise<any>) {
if (!consumerGroups.includes(options.group_name)) return console.error('GROUP NAME NOT ALLOWED');
console.log('Start reading loop')
try {
await this.client.xGroupCreate(options.stream_name, options.group_name, '0', { MKSTREAM: true });
} catch (ex) {
console.log('Group', options.group_name, 'already exist');
}
this.readFromStream(options.stream_name, options.group_name, options.consumer_name, processFunction);
}
static async addToStream(streamName: string, data: Record<string, string>) {
const result = await this.client.xAdd(streamName, "*", { ...data, timestamp: Date.now().toString() });
return result;
}
}

View File

@@ -0,0 +1,9 @@
export function requireEnv(name: string, errorMessage?: string) {
if (!process.env[name]) {
console.error(errorMessage || `ENV variable ${name} is required`);
return process.exit(1);
}
console.log('requireEnv', name, process.env[name]);
return process.env[name] as string;
}

View File

@@ -7,17 +7,11 @@ import { DeployHelper } from '../helpers/deploy-helper';
const TMP_PATH = path.join(__dirname, '../../tmp'); const TMP_PATH = path.join(__dirname, '../../tmp');
const LOCAL_PATH = path.join(__dirname, '../../dashboard'); const LOCAL_PATH = path.join(__dirname, '../../dashboard');
const REMOTE_PATH = '/home/testmode/litlyx/dashboard'; const REMOTE_PATH = '/home/litlyx/dashboard';
const ZIP_NAME = 'dashboard.zip'; const ZIP_NAME = 'dashboard.zip';
const TESTMODE_PORT = "4010";
const argvMode = process.argv[2] const MODE = DeployHelper.getMode();
const SKIP_BUILD = DeployHelper.getArgAt(0) == '--no-build';
if (argvMode != '--production' && argvMode != '--testmode') {
console.error('use --production or --testmode');
process.exit(1);
}
const MODE = argvMode === '--production' ? 'production' : 'testmode';
console.log('Deploying dashboard in mode:', MODE); console.log('Deploying dashboard in mode:', MODE);
@@ -28,20 +22,16 @@ async function main() {
if (fs.existsSync(TMP_PATH)) fs.rmSync(TMP_PATH, { force: true, recursive: true }); if (fs.existsSync(TMP_PATH)) fs.rmSync(TMP_PATH, { force: true, recursive: true });
fs.ensureDirSync(TMP_PATH); fs.ensureDirSync(TMP_PATH);
console.log('Building'); if (!SKIP_BUILD) {
child.execSync(`cd ${LOCAL_PATH} && pnpm i && pnpm run build`) console.log('Building');
child.execSync(`cd ${LOCAL_PATH} && pnpm i && pnpm run build`)
}
console.log('Creting zip file'); console.log('Creating zip file');
const archive = createZip(TMP_PATH + '/' + ZIP_NAME); const archive = createZip(TMP_PATH + '/' + ZIP_NAME);
archive.directory(LOCAL_PATH + '/.output', '/.output'); archive.directory(LOCAL_PATH + '/.output', '/.output');
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
if (MODE === 'testmode') {
const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
const devContent = ecosystemContent.replace(/name: '(.*?)'/, "name: 'test-$1'").replace(/3010/, TESTMODE_PORT);
archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
} else {
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
}
// archive.file(LOCAL_PATH + '/.env', { name: '/.env' }); // archive.file(LOCAL_PATH + '/.env', { name: '/.env' });
await archive.finalize(); await archive.finalize();

View File

@@ -5,10 +5,14 @@ import { createZip } from '../helpers/zip-helper';
import { DeployHelper } from '../helpers/deploy-helper'; import { DeployHelper } from '../helpers/deploy-helper';
const TMP_PATH = path.join(__dirname, '../../tmp'); const TMP_PATH = path.join(__dirname, '../../tmp');
const LOCAL_PATH = path.join(__dirname, '../../email'); const LOCAL_PATH = path.join(__dirname, '../../email');
const REMOTE_PATH = '/home/litlyx/email';
const ZIP_NAME = 'email.zip';
const REMOTE_PATH = '/home/production/litlyx/email'; const MODE = DeployHelper.getMode();
console.log('Deploying mail-service in mode:', MODE);
setTimeout(() => { main(); }, 3000);
async function main() { async function main() {
@@ -16,11 +20,9 @@ async function main() {
fs.ensureDirSync(TMP_PATH); fs.ensureDirSync(TMP_PATH);
console.log('Creting zip file'); console.log('Creting zip file');
const archive = createZip(TMP_PATH + '/email.zip'); const archive = createZip(TMP_PATH + '/' + ZIP_NAME);
archive.directory(LOCAL_PATH + '/dist', '/dist'); archive.directory(LOCAL_PATH + '/dist', '/dist');
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' }) archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
archive.file(LOCAL_PATH + '/package.json', { name: '/package.json' }); archive.file(LOCAL_PATH + '/package.json', { name: '/package.json' });
archive.file(LOCAL_PATH + '/pnpm-lock.yaml', { name: '/pnpm-lock.yaml' }); archive.file(LOCAL_PATH + '/pnpm-lock.yaml', { name: '/pnpm-lock.yaml' });
await archive.finalize(); await archive.finalize();
@@ -42,14 +44,14 @@ async function main() {
await scp.mkdir(REMOTE_PATH); await scp.mkdir(REMOTE_PATH);
console.log('Uploading zip file'); console.log('Uploading zip file');
await scp.uploadFile(TMP_PATH + '/email.zip', REMOTE_PATH + '/email.zip'); await scp.uploadFile(TMP_PATH + '/' + ZIP_NAME, REMOTE_PATH + '/' + ZIP_NAME);
scp.close(); scp.close();
console.log('Cleaning local'); console.log('Cleaning local');
fs.rmSync(TMP_PATH + '/email.zip', { force: true, recursive: true }); fs.rmSync(TMP_PATH + '/' + ZIP_NAME, { force: true, recursive: true });
console.log('Extracting remote'); console.log('Extracting remote');
await DeployHelper.execute(`cd ${REMOTE_PATH} && unzip email.zip && rm -r email.zip`); await DeployHelper.execute(`cd ${REMOTE_PATH} && unzip ${ZIP_NAME} && rm -r ${ZIP_NAME}`);
console.log('Installing remote'); console.log('Installing remote');
await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pnpm i`); await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pnpm i`);
@@ -58,7 +60,4 @@ async function main() {
ssh.dispose(); ssh.dispose();
} }
main();

View File

@@ -2,22 +2,37 @@
import { Client, ScpClient } from 'node-scp'; import { Client, ScpClient } from 'node-scp';
import { NodeSSH } from 'node-ssh' import { NodeSSH } from 'node-ssh'
import fs from 'fs-extra'; import fs from 'fs-extra';
import { REMOTE_HOST, IDENTITY_FILE } from '../.config' import { REMOTE_HOST_PRODUCTION, REMOTE_HOST_TESTMODE, IDENTITY_FILE } from '../.config'
export class DeployHelper { export class DeployHelper {
private static scpClient: ScpClient; private static scpClient: ScpClient;
private static sshClient: NodeSSH; private static sshClient: NodeSSH;
static getMode() {
const argvMode = process.argv[2]
if (argvMode != '--production' && argvMode != '--testmode') {
console.error('use --production or --testmode');
process.exit(1);
}
const MODE = argvMode === '--production' ? 'production' : 'testmode';
return MODE;
}
static getArgAt(index: number) {
return process.argv[3 + index];
}
static async connect() { static async connect() {
this.scpClient = await Client({ this.scpClient = await Client({
host: REMOTE_HOST, host: this.getMode() === 'production' ? REMOTE_HOST_PRODUCTION : REMOTE_HOST_TESTMODE,
username: 'root', username: 'root',
privateKey: fs.readFileSync(IDENTITY_FILE) privateKey: fs.readFileSync(IDENTITY_FILE)
}) })
this.sshClient = new NodeSSH(); this.sshClient = new NodeSSH();
await this.sshClient.connect({ await this.sshClient.connect({
host: REMOTE_HOST, host: this.getMode() === 'production' ? REMOTE_HOST_PRODUCTION : REMOTE_HOST_TESTMODE,
username: 'root', username: 'root',
privateKeyPath: IDENTITY_FILE privateKeyPath: IDENTITY_FILE
}); });

View File

@@ -12,8 +12,8 @@ export class SharedHelper {
clear() { clear() {
if (fs.existsSync(this.localSharedPath)) { if (fs.existsSync(this.localSharedPath)) {
fs.rmSync(this.localSharedPath, { force: true, recursive: true }); fs.rmSync(this.localSharedPath, { force: true, recursive: true });
fs.mkdirSync(this.localSharedPath);
} }
fs.mkdirSync(this.localSharedPath);
} }
create(name: string) { create(name: string) {

View File

@@ -0,0 +1,74 @@
import fs from 'fs-extra';
import path from 'path';
import { createZip } from '../helpers/zip-helper';
import { DeployHelper } from '../helpers/deploy-helper';
import { REMOTE_HOST_TESTMODE } from '../.config';
const TMP_PATH = path.join(__dirname, '../../tmp');
const LOCAL_PATH = path.join(__dirname, '../../producer');
const REMOTE_PATH = '/home/litlyx/producer';
const ZIP_NAME = 'producer.zip';
const MODE = DeployHelper.getMode();
console.log('Deploying producer in mode:', MODE);
setTimeout(() => { main(); }, 3000);
async function main() {
if (fs.existsSync(TMP_PATH)) fs.rmSync(TMP_PATH, { force: true, recursive: true });
fs.ensureDirSync(TMP_PATH);
console.log('Creting zip file');
const archive = createZip(TMP_PATH + '/' + ZIP_NAME);
archive.directory(LOCAL_PATH + '/dist', '/dist');
if (MODE === 'testmode') {
const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
const devContent = ecosystemContent.replace("redis://litlyx.com", `redis://${REMOTE_HOST_TESTMODE}`);
archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
} else {
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
}
archive.file(LOCAL_PATH + '/package.json', { name: '/package.json' });
archive.file(LOCAL_PATH + '/pnpm-lock.yaml', { name: '/pnpm-lock.yaml' });
await archive.finalize();
await DeployHelper.connect();
const { scp, ssh } = DeployHelper.instances();
console.log('Creating remote structure');
console.log('Check existing');
const remoteExist = await scp.exists(REMOTE_PATH);
console.log('Exist', remoteExist);
if (remoteExist) {
console.log('Deleting');
await DeployHelper.execute(`rm -r ${REMOTE_PATH}`);
}
console.log('Creating folder');
await scp.mkdir(REMOTE_PATH);
console.log('Uploading zip file');
await scp.uploadFile(TMP_PATH + '/' + ZIP_NAME, REMOTE_PATH + '/' + ZIP_NAME);
scp.close();
console.log('Cleaning local');
fs.rmSync(TMP_PATH + '/' + ZIP_NAME, { force: true, recursive: true });
console.log('Extracting remote');
await DeployHelper.execute(`cd ${REMOTE_PATH} && unzip ${ZIP_NAME} && rm -r ${ZIP_NAME}`);
console.log('Installing remote');
await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pnpm i`);
console.log('Executing remote');
await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pm2 start ecosystem.config.js`);
ssh.dispose();
}

View File

@@ -1 +1,13 @@
import { SharedHelper } from "../helpers/shared-helper";
import path from "node:path";
const helper = new SharedHelper(path.join(__dirname, '../../producer/src/shared'))
helper.clear();
helper.create('utils');
helper.copy('utils/requireEnv.ts');
helper.create('services');
helper.copy('services/RedisStreamService.ts');

View File

@@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"author": "Emily", "author": "Emily",
"license": "MIT", "license": "MIT",
"description": "", "description": "Shared files to be imported",
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.10", "@types/node": "^22.10.10",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",

View File

@@ -1,11 +1,12 @@
{ {
"extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"module": "NodeNext", "module": "NodeNext",
"target": "ESNext", "target": "ESNext"
"composite": true
}, },
"include": [ "include": [
"**/*.ts" "./**/*.ts"
], ],
"exclude": [
"node_modules"
]
} }

View File

@@ -4,5 +4,6 @@ export function requireEnv(name: string, errorMessage?: string) {
console.error(errorMessage || `ENV variable ${name} is required`); console.error(errorMessage || `ENV variable ${name} is required`);
return process.exit(1); return process.exit(1);
} }
console.log('requireEnv', name, process.env[name]);
return process.env[name] as string; return process.env[name] as string;
} }