Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45b2470fb4 | ||
|
|
9df9d1b75f | ||
| 6ccc186e97 | |||
|
|
8e5421ed17 |
13 changed files with 327 additions and 16 deletions
|
|
@ -1,8 +1,6 @@
|
|||
name: CI for GGS-CRON
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'release-*'
|
||||
|
|
|
|||
35
app.js
35
app.js
|
|
@ -15,11 +15,17 @@ const {
|
|||
getTestSpreadSheet,
|
||||
GoogleFunctions,
|
||||
PluginsManager,
|
||||
test_drive
|
||||
} = require("./lib/common");
|
||||
const { SyncText } = require("./lib/sync_text");
|
||||
const { startup } = require("./lib/zmq");
|
||||
|
||||
const { CronJobs } = require("./cron-jobs");
|
||||
const { google } = require("googleapis");
|
||||
const { EventEmitter } = require("stream");
|
||||
const fs = require('fs/promises');
|
||||
|
||||
// const nproc = require('./lib/nproc');
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
|
|
@ -36,7 +42,7 @@ app
|
|||
.use(express.json())
|
||||
.use(express.urlencoded({ extended: false }))
|
||||
.use(cookieParser())
|
||||
.use(express.static(path.join(__dirname, "public")));
|
||||
.use(express.static('public'));
|
||||
|
||||
app.use("/", indexRouter);
|
||||
app.use("/users", usersRouter);
|
||||
|
|
@ -53,6 +59,7 @@ var cronTasks =
|
|||
|
||||
const auth = GoogleFunctions.auth();
|
||||
const sheet = GoogleFunctions.SpreadSheets(auth);
|
||||
const drive = GoogleFunctions.Drive(auth);
|
||||
|
||||
// let heartbeatTask = CronJobs.doEveryMinute(() => {
|
||||
// Log.debug(`All running => ${JSON.stringify(cronTasks)}`);
|
||||
|
|
@ -84,12 +91,33 @@ const sheet = GoogleFunctions.SpreadSheets(auth);
|
|||
// GoogleFunctions.getPriceSlotValues(sheet, "tha");
|
||||
|
||||
// v3 !!!!
|
||||
GoogleFunctions.syncProfilePrice(sheet, "tha", false);
|
||||
// GoogleFunctions.syncProfilePrice(sheet, "tha", false);
|
||||
//
|
||||
|
||||
// Test Taobin SyncText
|
||||
// SyncText.run(sheet, "uae", false);
|
||||
|
||||
// Test drive
|
||||
// test_drive(drive);
|
||||
|
||||
// startup();
|
||||
//
|
||||
//
|
||||
|
||||
// let client = new nproc.NprocClient("127.0.0.1:36540", () => {
|
||||
// client.subscribe("self");
|
||||
// client.publish("self", {
|
||||
// msg: "test"
|
||||
// });
|
||||
|
||||
// if(client.history.length > 1){
|
||||
// Log.info("connected!");
|
||||
// }
|
||||
// }, () => {
|
||||
// Log.debug("closed connection");
|
||||
// });
|
||||
|
||||
|
||||
const pm = new PluginsManager(cronTasks, CronJobs);
|
||||
pm.load();
|
||||
|
||||
|
|
@ -109,6 +137,9 @@ for (let scriptName of Object.keys(pm.scripts)) {
|
|||
endpointMap = undefined; // reset
|
||||
}
|
||||
|
||||
// const filePath = new URL('./package.json', import.meta.url);
|
||||
// const contents = await readFile(filePath, { encoding: 'utf8' });
|
||||
|
||||
// pm.loop()
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
|
|
|
|||
|
|
@ -181,6 +181,8 @@ function getSlotFunctionByIndex(index) {
|
|||
return (price) => "HIDE";
|
||||
case 9:
|
||||
return (price) => price * 0.60;
|
||||
case 17:
|
||||
return (price) => price * 4.0;
|
||||
default:
|
||||
return (price) => price;
|
||||
}
|
||||
|
|
@ -200,6 +202,7 @@ const ProfilePrice = {
|
|||
changelogs: [
|
||||
"2/4/25 initialized version 2, fix timeout on trigger",
|
||||
"8/4/25 initialized version 3, express server",
|
||||
"17/11/25 add slot 17 price times 4"
|
||||
],
|
||||
maximum_slots: MAXIMUM_SLOTS,
|
||||
};
|
||||
|
|
@ -293,7 +296,8 @@ function saveJsonToFile(filename, content) {
|
|||
// GOOGLE
|
||||
// ======================================================================
|
||||
|
||||
const { google, sheets_v4 } = require("googleapis");
|
||||
const { google, sheets_v4, drive_v3 } = require("googleapis");
|
||||
const { GoogleAuth } = require("google-auth-library");
|
||||
|
||||
function authorize() {
|
||||
const oauthClient = new google.auth.GoogleAuth({
|
||||
|
|
@ -301,7 +305,7 @@ function authorize() {
|
|||
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
|
||||
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/gm, "\n"),
|
||||
},
|
||||
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
|
||||
scopes: ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive"],
|
||||
});
|
||||
return oauthClient;
|
||||
}
|
||||
|
|
@ -325,6 +329,7 @@ function getCountrySpreadSheetId(cnt) {
|
|||
const GoogleFunctions = {
|
||||
auth: authorize,
|
||||
SpreadSheets: (auth) => google.sheets({ version: "v4", auth }),
|
||||
Drive: (auth) => google.drive({ version: 'v3', auth }),
|
||||
GetCountrySpreadSheet: getCountrySpreadSheetById,
|
||||
getAllSheetNamesByCountry: getAllSheetNamesByCountry,
|
||||
getCountrySheetByName: getCountrySheetByName,
|
||||
|
|
@ -1282,6 +1287,40 @@ async function _finalizeSyncProfilePrice(
|
|||
}
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// DRIVE
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* @param {drive_v3.Drive} drive instance
|
||||
*/
|
||||
async function test_drive(drive){
|
||||
|
||||
try {
|
||||
await drive.files.create({
|
||||
requestBody: {
|
||||
name: "test",
|
||||
mimeType: "text/plain",
|
||||
parents: [process.env.TAOBIN_ADMIN_SERVER_DRIVE_FOLDER]
|
||||
},
|
||||
media: {
|
||||
mimeType: "text/plain",
|
||||
body: "Hello from js"
|
||||
}
|
||||
}).then((x) => Log.info(`Created file response: ${JSON.parse(x)}`));
|
||||
} catch(error){
|
||||
Log.err(`Error test drive: ${error}`);
|
||||
return JSON.stringify({
|
||||
error: error
|
||||
});
|
||||
}
|
||||
return JSON.stringify({
|
||||
status: "success"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// special
|
||||
|
||||
// ======================================================================
|
||||
|
|
@ -1406,4 +1445,5 @@ module.exports = {
|
|||
getCountrySheetByName,
|
||||
diff2DArraysCustom,
|
||||
saveJsonToFile,
|
||||
test_drive
|
||||
};
|
||||
|
|
|
|||
85
lib/nproc.js
Normal file
85
lib/nproc.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
const net = require('net');
|
||||
// TODO: must change to read by env
|
||||
const API_KEY = Buffer.from('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
||||
|
||||
function frame(msgType, topicStr, bodyBuf) {
|
||||
const topic = Buffer.from(topicStr);
|
||||
const payloadLen = 1 + 2 + 4 + topic.length + bodyBuf.length;
|
||||
const totalBytes = payloadLen + 32;
|
||||
|
||||
const buf = Buffer.alloc(4 + payloadLen + 32);
|
||||
buf.writeUInt32BE(totalBytes, 0);
|
||||
buf.writeUInt8(msgType, 4);
|
||||
buf.writeUInt16BE(topic.length, 5);
|
||||
buf.writeUInt32BE(bodyBuf.length, 7);
|
||||
topic.copy(buf, 11);
|
||||
bodyBuf.copy(buf, 11 + topic.length);
|
||||
API_KEY.copy(buf, 4 + payloadLen);
|
||||
return buf;
|
||||
}
|
||||
|
||||
class NprocClient {
|
||||
|
||||
MESSAGE_TYPE = {
|
||||
SUB: 1,
|
||||
UNSUB: 2,
|
||||
PUB: 3,
|
||||
PING: 4
|
||||
};
|
||||
|
||||
constructor(
|
||||
addr,
|
||||
onConnect,
|
||||
onClose
|
||||
){
|
||||
let _addr = addr.toString().split(":");
|
||||
|
||||
let host = _addr[0];
|
||||
let port = parseInt(_addr[1]);
|
||||
|
||||
this.client = net.createConnection({
|
||||
host: host,
|
||||
port: port
|
||||
}, () => onConnect());
|
||||
this.history = [];
|
||||
this.acc = Buffer.alloc(0);
|
||||
this.client.on('data', chunk => {
|
||||
this.acc = Buffer.concat([this.acc, chunk]);
|
||||
while(this.acc.length >= 4){
|
||||
const total = this.acc.readUInt32BE(0);
|
||||
if (this.acc.length < 4 + total) break;
|
||||
const frm = this.acc.subarray(0, 4 + total);
|
||||
this.acc = this.acc.subarray(4 + total);
|
||||
|
||||
const msgType = frm.readUInt8(4);
|
||||
const topicLen = frm.readUInt16BE(5);
|
||||
const bodyLen = frm.readUInt32BE(7);
|
||||
const topic = frm.subarray(11, 11 + topicLen).toString();
|
||||
const body = frm.subarray(11 + topicLen, 11 + topicLen + bodyLen);
|
||||
|
||||
let res = {
|
||||
msgType, topic, body: body.toString()
|
||||
};
|
||||
console.log(`get msg! ${JSON.stringify(res)}`);
|
||||
|
||||
this.history.push(res);
|
||||
}
|
||||
});
|
||||
this.client.on('close', () => onClose());
|
||||
}
|
||||
|
||||
subscribe(topic){
|
||||
this.client.write(frame(this.MESSAGE_TYPE.SUB, topic, Buffer.alloc(0)));
|
||||
}
|
||||
|
||||
publish(topic, payload){
|
||||
this.client.write(frame(this.MESSAGE_TYPE.PUB, topic, Buffer.from(JSON.stringify(payload))));
|
||||
}
|
||||
|
||||
// TODO: unsub
|
||||
// TODO: ping
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NprocClient
|
||||
};
|
||||
0
lib/package_manager.js
Normal file
0
lib/package_manager.js
Normal file
28
lib/zmq.js
Normal file
28
lib/zmq.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
const zmq = require('zeromq');
|
||||
const uuid = require('uuid');
|
||||
|
||||
/**
|
||||
* Publish simple message to server to notify service is ready.
|
||||
*/
|
||||
async function startup(){
|
||||
const pub = new zmq.Publisher();
|
||||
await pub.connect("tcp://127.0.0.1:36541");
|
||||
|
||||
const msg = {
|
||||
id: uuid.v4(),
|
||||
topic: "news",
|
||||
message_type: "Data",
|
||||
data: { content: "GGS Ready!" },
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
await pub.send(["news", JSON.stringify(msg)]);
|
||||
|
||||
console.log("startup send!")
|
||||
pub.close();
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
startup
|
||||
};
|
||||
35
package-lock.json
generated
35
package-lock.json
generated
|
|
@ -18,7 +18,11 @@
|
|||
"morgan": "~1.9.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"zeromq": "^6.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
|
|
@ -720,6 +724,14 @@
|
|||
"wordwrap": "0.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cmake-ts": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cmake-ts/-/cmake-ts-1.0.2.tgz",
|
||||
"integrity": "sha512-5l++JHE7MxFuyV/OwJf3ek7ZZN1aGPFPM5oUz6AnK5inQAPe4TFXRMz5sA2qg2FRgByPWdqO+gSfIPo8GzoKNQ==",
|
||||
"bin": {
|
||||
"cmake-ts": "build/main.js"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
|
||||
|
|
@ -1631,6 +1643,14 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||
|
|
@ -2273,6 +2293,19 @@
|
|||
"decamelize": "^1.0.0",
|
||||
"window-size": "0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zeromq": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.5.0.tgz",
|
||||
"integrity": "sha512-vWOrt19lvcXTxu5tiHXfEGQuldSlU+qZn2TT+4EbRQzaciWGwNZ99QQTolQOmcwVgZLodv+1QfC6UZs2PX/6pQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"cmake-ts": "1.0.2",
|
||||
"node-addon-api": "^8.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"morgan": "~1.9.1",
|
||||
"node-cron": "^3.0.3",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"zeromq": "^6.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.8"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ const heartbeatApiInfo = {
|
|||
let heartbeatTask = CronJobs.doEveryMinute(() => {
|
||||
Log.debug("[hb] test heartbeat");
|
||||
Log.debug(`[hb] current running tasks: ${JSON.stringify(CronJobs.getAllRunning.size)}`);
|
||||
heartbeatTask.stop();
|
||||
client.publish("log", {
|
||||
msg: "heartbeat",
|
||||
status: 200
|
||||
});
|
||||
}, 'heartbeat');
|
||||
|
||||
heartbeatTask.on('stop-heartbeat', () => heartbeatTask.stop());
|
||||
|
|
@ -28,4 +31,7 @@ endpointMap = {
|
|||
|
||||
res.render("heartbeat");
|
||||
},
|
||||
'info': function(req, res, next){
|
||||
res.json(heartbeatApiInfo);
|
||||
}
|
||||
};
|
||||
|
|
@ -26,12 +26,19 @@ endpointMap = {
|
|||
|
||||
res.render("apiInfo");
|
||||
},
|
||||
"run/now": function (req, res, next) {
|
||||
GoogleFunctions.syncProfilePrice(sheet, "tha", false);
|
||||
res.send("Force run `Sync Thai Price Slots`");
|
||||
"info": function(req, res, next) {
|
||||
res.json(syncThaiProfilePriceSlotApiInfo);
|
||||
},
|
||||
"run/test": function (req, res, next) {
|
||||
GoogleFunctions.syncProfilePrice(sheet, "tha", true);
|
||||
res.send("Force run `Sync Thai Price Slots` test mode");
|
||||
"run/now": async function (req, res, next) {
|
||||
// res.json({ status: "running" });
|
||||
await GoogleFunctions.syncProfilePrice(sheet, "tha", false);
|
||||
res.json({ status: "success", message: "Force run `Sync Thai Price Slots`" });
|
||||
// res.send("Force run `Sync Thai Price Slots`");
|
||||
},
|
||||
"run/test": async function (req, res, next) {
|
||||
// res.json({ status: "running" });
|
||||
await GoogleFunctions.syncProfilePrice(sheet, "tha", true);
|
||||
res.json({ status: "success", message: "Force run `Sync Thai Price Slots` test mode" });
|
||||
// res.send("Force run `Sync Thai Price Slots` test mode");
|
||||
},
|
||||
};
|
||||
|
|
|
|||
58
public/index.html
Normal file
58
public/index.html
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css"
|
||||
integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/test.js"></script>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<h1>Heartbeat</h1>
|
||||
<button
|
||||
class="pure-button pure-button-primary"
|
||||
onclick="getHeartBeatInfo()"
|
||||
>
|
||||
Info
|
||||
</button>
|
||||
<h1>Sync Price Slot (THA)</h1>
|
||||
<button
|
||||
class="pure-button pure-button-primary"
|
||||
onclick="getSyncPriceInfo()"
|
||||
>
|
||||
Info
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="pure-button pure-button-primary"
|
||||
onclick="setSyncPriceRunTest()"
|
||||
>
|
||||
Test
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="pure-button pure-button-primary"
|
||||
onclick="setSyncPriceRunNow()"
|
||||
>
|
||||
Run Now
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u-1-2" style="background-color: aliceblue">
|
||||
<!-- show output -->
|
||||
<h1>Output</h1>
|
||||
<textarea
|
||||
id="output"
|
||||
cols="100"
|
||||
rows="45"
|
||||
readonly
|
||||
style="resize: none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
24
public/scripts/test.js
Normal file
24
public/scripts/test.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
console.log("Hello, World!");
|
||||
|
||||
function sendToOutput(message){
|
||||
document.getElementById("output").value += message + "\n";
|
||||
}
|
||||
|
||||
function getHeartBeatInfo(){
|
||||
fetch("http://localhost:36530/heartbeat/info").then(response => response.json()).then(data => sendToOutput(JSON.stringify(data)));
|
||||
}
|
||||
|
||||
// Sync Price Slot Command Shortcuts
|
||||
//
|
||||
//
|
||||
function getSyncPriceInfo(){
|
||||
fetch("http://localhost:36530/syncProfilePriceSlotSheet/info").then(response => response.json()).then(data => sendToOutput(JSON.stringify(data)));
|
||||
}
|
||||
|
||||
function setSyncPriceRunTest(){
|
||||
fetch("http://localhost:36530/syncProfilePriceSlotSheet/run/test").then(response => response.json()).then(data => sendToOutput(JSON.stringify(data)));
|
||||
}
|
||||
|
||||
function setSyncPriceRunNow(){
|
||||
fetch("http://localhost:36530/syncProfilePriceSlotSheet/run/now").then(response => response.json()).then(data => sendToOutput(JSON.stringify(data)));
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
DEBUG=server:* PORT=36530 npm start
|
||||
DEBUG=server:* PORT=36531 npm start
|
||||
Loading…
Add table
Add a link
Reference in a new issue