mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
updates for testmode
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ docker
|
|||||||
dev
|
dev
|
||||||
docker-compose.admin.yml
|
docker-compose.admin.yml
|
||||||
full_reload.sh
|
full_reload.sh
|
||||||
tmp
|
tmp
|
||||||
|
ecosystem.config.js
|
||||||
1
dashboard/.gitignore
vendored
1
dashboard/.gitignore
vendored
@@ -24,6 +24,7 @@ winston-*.ndjson
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
ecosystem.config.js
|
||||||
|
|
||||||
# Test reports
|
# Test reports
|
||||||
*.report.txt
|
*.report.txt
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>. We’re 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>. We’re 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>`
|
||||||
`
|
|
||||||
@@ -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
1
producer/.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
87
producer/pnpm-lock.yaml
generated
87
producer/pnpm-lock.yaml
generated
@@ -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: {}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -21,9 +21,9 @@ app.post('/event', express.json(jsonOptions), async (req, res) => {
|
|||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
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();
|
||||||
|
|||||||
90
producer/src/shared/services/RedisStreamService.ts
Normal file
90
producer/src/shared/services/RedisStreamService.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
producer/src/shared/utils/requireEnv.ts
Normal file
9
producer/src/shared/utils/requireEnv.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ export class SharedHelper {
|
|||||||
constructor(private localSharedPath: string) { }
|
constructor(private localSharedPath: string) { }
|
||||||
|
|
||||||
static getSharedPath() { return path.join(__dirname, '../../shared_global'); }
|
static getSharedPath() { return path.join(__dirname, '../../shared_global'); }
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
74
scripts/producer/deploy.ts
Normal file
74
scripts/producer/deploy.ts
Normal 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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user