From ac0064bfa9fd9be419a7bc7ebdb73c16a18af79e Mon Sep 17 00:00:00 2001 From: mx00 <2784107+mx00@users.noreply.github.com> Date: Fri, 15 May 2026 23:00:36 -0700 Subject: [PATCH 1/6] Display entry and exit prices on Trades tab in the trade details --- .gitignore | 1 + src/views/Daily.vue | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.gitignore b/.gitignore index 9f69cbb8..b28ec4c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist .vscode logs .idea/ +docker-compose-local-MINE.yml diff --git a/src/views/Daily.vue b/src/views/Daily.vue index a68afab0..525d3fa2 100644 --- a/src/views/Daily.vue +++ b/src/views/Daily.vue @@ -948,6 +948,9 @@ function getOHLC(date, symbol, type) { Position Entry + Entry Price + Exit + Exit Price P&L/Sec @@ -1002,6 +1005,31 @@ function getOHLC(date, symbol, type) { v-bind:data-bs-title="'Swing trade from ' + useDateCalFormat(trade.entryTime)"> + + {{ + (trade.entryPrice).toFixed(5) + }}{{ + useTwoDecCurrencyFormat(trade.entryPrice) + }} + + + + {{ useTimeFormat(trade.exitTime) }} + + + {{ + (trade.exitPrice).toFixed(5) + }}{{ + useTwoDecCurrencyFormat(trade.exitPrice) + }} + Date: Mon, 18 May 2026 21:36:49 -0700 Subject: [PATCH 2/6] Fix manual screenshot tag fallback --- src/utils/daily.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/daily.js b/src/utils/daily.js index 70676857..eb23685f 100644 --- a/src/utils/daily.js +++ b/src/utils/daily.js @@ -542,7 +542,7 @@ export const useUpdateTags = async () => { query.equalTo("tradeId", tradeTagsId.value) } else { //it's the case when changing daily tags, tradeTagsId.value is null (see useTradeTagsChange) - query.equalTo("tradeId", tradeTagsDateUnix.value.toString()) + query.equalTo("tradeId", (tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || screenshot.name || Date.now()).toString()) } } const results = await query.first(); @@ -578,12 +578,12 @@ export const useUpdateTags = async () => { } else { if (tradeTagsId.value) { - object.set("dateUnix", tradeTagsDateUnix.value) + object.set("dateUnix", tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || Date.now()) object.set("tradeId", tradeTagsId.value) } else { //it's the case when changing daily tags, tradeTagsId.value is null - object.set("dateUnix", tradeTagsDateUnix.value) - object.set("tradeId", tradeTagsDateUnix.value.toString()) + object.set("dateUnix", tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || Date.now()) + object.set("tradeId", (tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || screenshot.name || Date.now()).toString()) } } From ab50cf5358045d87011b3f5137e36cd7d1f99c6e Mon Sep 17 00:00:00 2001 From: mx00 <2784107+mx00@users.noreply.github.com> Date: Mon, 18 May 2026 23:03:44 -0700 Subject: [PATCH 3/6] Fix manual chart screenshot upload linking --- src/utils/screenshots.js | 5 +++-- src/views/Daily.vue | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/screenshots.js b/src/utils/screenshots.js index 249c57b4..05ba1c2a 100644 --- a/src/utils/screenshots.js +++ b/src/utils/screenshots.js @@ -182,7 +182,7 @@ async function imgFileReader(param) { }) } -export async function useSetupImageUpload(event, param1, param2, param3) { +export async function useSetupImageUpload(event, param1, param2, param3, tradeId = null) { tradeScreenshotChanged.value = true if (pageId.value == "daily") { saveButton.value = true @@ -191,6 +191,7 @@ export async function useSetupImageUpload(event, param1, param2, param3) { screenshot.dateUnix = param1 screenshot.symbol = param2 screenshot.side = param3 + screenshot.tradeId = tradeId } const file = event.target.files[0]; @@ -429,7 +430,7 @@ export async function useSaveScreenshot() { //console.log(" -> dateUnix " + screenshot.dateUnix) - screenshot.side ? screenshot.name = "t" + screenshot.dateUnix + "_" + screenshot.symbol + "_" + screenshot.side : screenshot.name = screenshot.dateUnix + "_" + screenshot.symbol + screenshot.tradeId ? screenshot.name = screenshot.tradeId : (screenshot.side ? screenshot.name = "t" + screenshot.dateUnix + "_" + screenshot.symbol + "_" + screenshot.side : screenshot.name = screenshot.dateUnix + "_" + screenshot.symbol) //console.log("name " + screenshot.name) diff --git a/src/views/Daily.vue b/src/views/Daily.vue index 525d3fa2..12bd240e 100644 --- a/src/views/Daily.vue +++ b/src/views/Daily.vue @@ -1396,7 +1396,7 @@ function getOHLC(date, symbol, type) {
+ @change="useSetupImageUpload($event, filteredTrades[itemTradeIndex].trades[tradeIndex].entryTime, filteredTrades[itemTradeIndex].trades[tradeIndex].symbol, filteredTrades[itemTradeIndex].trades[tradeIndex].side, filteredTrades[itemTradeIndex].trades[tradeIndex].id)" />
From c881229715e185d554bddd0f6c3fd4844e81dd46 Mon Sep 17 00:00:00 2001 From: mx00 <2784107+mx00@users.noreply.github.com> Date: Sun, 24 May 2026 22:54:43 -0700 Subject: [PATCH 4/6] Fix screenshot uploads and trade screenshot linking --- index.mjs | 8 +- index.mjs.bak | 570 +++++++++++++++++++++++++++++++++++++++ src/utils/screenshots.js | 1 + 3 files changed, 576 insertions(+), 3 deletions(-) create mode 100644 index.mjs.bak diff --git a/index.mjs b/index.mjs index 5c49cfd9..319e5eb3 100644 --- a/index.mjs +++ b/index.mjs @@ -46,7 +46,8 @@ console.log(' -> Database URI ' + hiddenDatabaseURI) let tradenoteDatabase = process.env.TRADENOTE_DATABASE var app = express(); -app.use(express.json()); +app.use(express.json({ limit: '50mb' })); +app.use(express.urlencoded({ limit: '50mb', extended: true })); const port = process.env.TRADENOTE_PORT; const PROXY_PORT = 39482; @@ -339,7 +340,8 @@ const setupApiRoutes = (app) => { - app.use(express.json()); + app.use(express.json({ limit: '50mb' })); +app.use(express.urlencoded({ limit: '50mb', extended: true })); let allUsers const getAllUsers = async () => { @@ -498,7 +500,7 @@ const startIndex = async () => { resolve(); } else { // In production, handle API routes normally - app.use('/api/*', express.json(), (req, res, next) => { + app.use('/api/*', express.json({ limit: '50mb' }), express.urlencoded({ limit: '50mb', extended: true }), (req, res, next) => { //console.log(`Received API request: ${req.method} ${req.url}`); next(); // Pass control to specific API handlers }); diff --git a/index.mjs.bak b/index.mjs.bak new file mode 100644 index 00000000..5c49cfd9 --- /dev/null +++ b/index.mjs.bak @@ -0,0 +1,570 @@ +import express from 'express'; +import { ParseServer } from 'parse-server' +//var ParseDashboard = require('parse-dashboard'); +import ParseNode from 'parse/node.js' +import path from 'path' +import fs from 'fs' +import axios from 'axios' +import * as Vite from 'vite' +import { MongoClient } from "mongodb" +import Proxy from 'http-proxy' +import { useImportTrades, useGetExistingTradesArray, useUploadTrades } from './src/utils/addTrades.js'; +import { currentUser, uploadMfePrices } from './src/stores/globals.js'; +import { useGetTimeZone } from './src/utils/utils.js'; +import Stripe from 'stripe'; + + +/* STRIPE VAR */ +let stripeSk //secret key +let stripePk // public key +let stripePriceId +let stripeTrialPeriod + +if (process.env.STRIPE_SK) { + stripeSk = new Stripe(process.env.STRIPE_SK); + stripePk = process.env.STRIPE_PK + stripePriceId = process.env.STRIPE_PRICE_ID + stripeTrialPeriod = process.env.STRIPE_TRIAL_PERIOD +} + +/* END STRIPE */ + +let databaseURI + +if (process.env.MONGO_URI) { + databaseURI = process.env.MONGO_URI +} else if (process.env.MONGO_ATLAS) { + databaseURI = "mongodb+srv://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin" +} else { + databaseURI = "mongodb://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + ":" + process.env.MONGO_PORT + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin" +} + +console.log("\nCONNECTING TO MONGODB") +let hiddenDatabaseURI = databaseURI.replace(/:\/\/[^@]*@/, "://***@") +console.log(' -> Database URI ' + hiddenDatabaseURI) + +let tradenoteDatabase = process.env.TRADENOTE_DATABASE + +var app = express(); +app.use(express.json()); + +const port = process.env.TRADENOTE_PORT; +const PROXY_PORT = 39482; + +// SERVER + +let server = null + +export let allowRegister = false + + +/**************************** APIs ****************************/ + +const setupApiRoutes = (app) => { + + app.post("/api/parseAppId", (req, res) => { + //console.log("\nAPI : post APP ID") + //console.log(process.env.APP_ID) + res.send(process.env.APP_ID) + }); + + app.post("/api/registerPage", (req, res) => { + //console.log("\nAPI : post APP ID") + //console.log(" REGISTER_OFF "+process.env.REGISTER_OFF) + res.send(process.env.REGISTER_OFF) + }); + + app.post("/api/posthog", (req, res) => { + //console.log("\nAPI : posthog") + if (process.env.ANALYTICS_OFF) { + res.send("off") + } else { + res.send("phc_FxkjH1O898jKu0yiELC3aWKda3vGov7waGN0weU5kw0") + } + }); + + + /********************************************** + * CLOUD / STRIPE + **********************************************/ + + app.post("/api/checkCloudPayment", async (req, res) => { + // Used for checking if can access add*, in case it's a paying user + let currentUser = req.body.currentUser + //console.log(" currentUser "+JSON.stringify(currentUser)) + //console.log(" current user " + JSON.stringify(req.body.currentUser)) + if (process.env.STRIPE_SK) { + //console.log("\nAPI : checkCloudPayment") + // Check if user is stripe customer + + // Check if user has paying customer + if (currentUser.hasOwnProperty("paymentService") && currentUser.paymentService.hasOwnProperty("subscriptionId")) { + /// if yes, let inn, status 200 + const activeSubscription = ['active', 'trialing', 'past_due'] + const subscription = await stripeSk.subscriptions.retrieve(currentUser.paymentService.subscriptionId) + if (activeSubscription.includes(subscription.status)) { + console.log(" -> User has valid subscription."); + res.status(200).send('OK'); + } else { + console.log(" -> User has invalid subscription."); + res.status(403).send('Forbidden'); + } + + } + + /// If not, check if user is within trial period + else { + + // Convert createdAt to a Date object + const createdAtDate = new Date(currentUser.createdAt); + + // Get the current time + const currentDate = new Date(); + + // Calculate the time difference in milliseconds + const timeDifference = currentDate - createdAtDate; + + // Convert the time difference to days + const differenceInDays = timeDifference / (1000 * 60 * 60 * 24); // Milliseconds to days + + //// if older, redirect to stripe / status 403 + if (differenceInDays > stripeTrialPeriod) { + console.log(" -> User is past trial period."); + res.status(403).send('Forbidden'); + } + + //// else, let inn, status 200 + else { + console.log(" -> User is within trial period."); + res.status(200).send('OK'); + } + } + + + + + } else { + res.status(200).send('OK'); + } + }); + + app.post('/api/create-checkout-session', async (req, res) => { + + let return_url + if (process.env.NODE_ENV == 'dev') { + return_url = `http://localhost:${port}/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}` + }else{ + return_url = `https://app.tradenote.co/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}` + } + + //console.log(" -> return_url : "+return_url) + const session = await stripeSk.checkout.sessions.create({ + ui_mode: 'embedded', + line_items: [ + { + // Provide the exact Price ID (for example, pr_1234) of the product you want to sell + price: stripePriceId, + quantity: 1, + }, + ], + mode: 'subscription', + return_url: return_url, + automatic_tax: { enabled: true }, + }); + + res.send({ clientSecret: session.client_secret }); + }); + + app.get('/api/getStripePk', async (req, res) => { + res.status(200).send(stripePk); + }) + + app.get('/api/session-status', async (req, res) => { + try { + console.log("Getting session status"); + const session = await stripeSk.checkout.sessions.retrieve(req.query.session_id); + + //console.log("Session retrieved:", JSON.stringify(session)); + + res.send({ + session: session, + status: session.status, + customer_email: session.customer_details.email, + customer_id: session.customer + }); + } catch (error) { + console.error("Error retrieving session:", error.message); + res.status(500).send({ error: error.message }); + } + }); + + /******************* END CLOUD ****************************/ + + app.get('/api/dockerVersion', async (req, res) => { + console.log("Getting Docker Version"); + await axios.get("https://hub.docker.com/v2/namespaces/eleventrading/repositories/tradenote/tags") + .then((response) => { + //console.log(" -> data " + JSON.stringify(response.data)) + res.status(200).send(response.data); + }) + .catch((error) => { + res.status(500).send({ error: error.message }); + }) + .finally(function () { + // always executed + }) + }); + + app.post("/api/updateSchemas", async (req, res) => { + + if (!process.env.STRIPE_SK || process.env.UPSERT_SCHEMA) { + console.log("\nAPI : post update schema") + + let rawdata = fs.readFileSync('requiredClasses.json'); + let schemasJson = JSON.parse(rawdata); + //console.log("schemasJson "+JSON.stringify(schemasJson)) + + let existingSchema = [] + const getExistingSchema = await ParseNode.Schema.all() + //console.log(" -> Get existing schema " + JSON.stringify(getExistingSchema)) + + /* 1- Rename legacy names in mongoDB */ + const renameMongoDb = (param1, param2) => { + return new Promise(async (resolve, reject) => { + console.log(" -> Renaming class " + param1 + " to " + param2) + MongoClient.connect(databaseURI).then(async (client) => { + console.log(" --> Connected to MongoDB") + const connect = client.db(tradenoteDatabase); + const allCollections = await connect.listCollections().toArray() + //console.log("allCollections "+JSON.stringify(allCollections)) + let collectionExists = allCollections.filter(obj => obj.name == param1) + //console.log(" --> collectionExists "+collectionExists.length) + if (collectionExists.length > 0) { + const collection = connect.collection(param1); + collection.rename(param2).then(() => { + console.log(" -> Renamed class successfully"); + resolve() + }) + } else { + console.log(" -> Collection doesn't exist.") + resolve() + } + + }).catch((err) => { + console.log(" -> Error renaming MongoDB class: " + err.Message); + reject() + }) + }) + } + + for (let i = 0; i < getExistingSchema.length; i++) { + //console.log("Class name " + getExistingSchema[i].className) + + //we check for classes/collections that need to be renamed + if (getExistingSchema[i].className == "setupsEntries" || getExistingSchema[i].className == "journals") { + let oldName = getExistingSchema[i].className + let newName + + if (getExistingSchema[i].className == "setupsEntries") newName = "screenshots" + if (getExistingSchema[i].className == "journals") newName = "diaries" + if (getExistingSchema[i].className == "patternsMistakes") newName = "setups" + + await renameMongoDb(oldName, newName) + } else { + existingSchema.push(getExistingSchema[i].className) + } + } + //console.log(" -> Existing Schema " + existingSchema) + + /* 2- Update or save new schemas in mongoDB */ + const updateSaveSchema = (param1, param2, param3) => { + return new Promise((resolve, reject) => { + const mySchema = new ParseNode.Schema(param1); + if (param2[param3].type === "String") mySchema.addString(param3) + if (param2[param3].type === "Number") mySchema.addNumber(param3) + if (param2[param3].type === "Boolean") mySchema.addBoolean(param3) + if (param2[param3].type === "Date") mySchema.addDate(param3) + if (param2[param3].type === "File") mySchema.addFile(param3) + if (param2[param3].type === "GeoPoint") mySchema.addGeoPoint(param3) + if (param2[param3].type === "Polygon") mySchema.addPolygon(param3) + if (param2[param3].type === "Array") mySchema.addArray(param3) + if (param2[param3].type === "Object") mySchema.addObject(param3) + if (param2[param3].type === "Pointer") mySchema.addPointer(param3, param2[param3].targetClass) + if (param2[param3].type === "Relation") mySchema.addRelation(param3, param2[param3].targetClass) + + //console.log("existing schema "+existingSchema) + //console.log("includes ? "+existingSchema.includes(className)) + + //If ParseNode (existing) schema includes the class name from required classes then update (just in case). Else add, and then add that class to existing schema array + if (existingSchema.includes(param1)) { + mySchema.update().then((result) => { + console.log(" --> Updating field " + param3) + //console.log(" -> Updated schema " + JSON.stringify(result)) + resolve() + }) + } else { + mySchema.save().then((result) => { + //console.log(" -> Save new schema " + JSON.stringify(result)) + console.log(" --> Saving field " + param3) + existingSchema.push(param1) // Once saved, we update for the rest of the fields, so we need to push to existingSchema + //console.log(" -> Existing Schema " + existingSchema) + resolve() + }) + } + }) + } + + for (let i = 0; i < schemasJson.length; i++) { + //console.log("el " + schemasJson[i].className) + let className = schemasJson[i].className + console.log(" -> Upsert class/collection " + className + " in ParseNode Schema") + let obj = schemasJson[i].fields + for (const key of Object.keys(obj)) { + //console.log(key, obj[key]); + if (key != "objectId" && key != "updatedAt" && key != "createdAt" && key != "ACL") { + //console.log(" -> Key " + key) + await updateSaveSchema(className, obj, key) + } + + } + } + + res.send({ "existingSchema": existingSchema }) + } else { + res.status(200).send('OK'); + } + + }) + + + + + app.use(express.json()); + + let allUsers + const getAllUsers = async () => { + console.log(" -> Getting all users") + return new Promise(async (resolve, reject) => { + const parseObject = ParseNode.Object.extend("_User"); + const query = new ParseNode.Query(parseObject); + const results = await query.find({ useMasterKey: true }); + allUsers = JSON.parse(JSON.stringify(results)) + resolve() + }) + } + + const validateApiKey = async (req, res, next) => { + await getAllUsers() + const targetKey = req.headers['api-key'] || req.query['api-key']; + //console.log(" -> target Key " + targetKey) + + const checkIPKey = (allUsers, targetKey) => { + for (const user of Object.values(allUsers)) { + if (user.hasOwnProperty("apis")) { + const index = user.apis.findIndex(obj => obj.key === targetKey); + if (index !== -1) { + currentUser.value = user + return true; + } + } + + } + return -1; // Return -1 if not found + } + + // Usage example + const hasIPKey = checkIPKey(allUsers, targetKey); + + if (hasIPKey) { + console.log(" -> Valid api key found :)") + next(); + } else { + console.log(" -> Invalid api key") + return res.status(401).send({ error: 'Invalid API key' }); + } + } + + app.post('/api/trades', validateApiKey, async (req, res) => { + const data = req.body; + try { + if (data && !data.data.length > 0) { + res.status(200).send(" -> No trades to import"); + } + else { + + uploadMfePrices.value = data.uploadMfePrices + + //console.log(" uploadMfePrices "+uploadMfePrices.value) + // Call the function from addTrades.js + + await useGetTimeZone() + await useGetExistingTradesArray("api", ParseNode) + await useImportTrades(data.data, "api", data.selectedBroker, ParseNode) + await useUploadTrades("api", ParseNode) + + res.status(200).send(" -> Saved Trades to ParseNode DB"); + } + } catch (error) { + console.error(error); + res.status(500).send({ error: 'Error creating executions' }); + } + }); + + app.post('/api/databento', async (req, res) => { + //console.log(" calling databento") + const data = req.body; + //console.log(" data "+JSON.stringify(data)) + try { + const username = data.username; + const password = ''; + + + let config = { + method: 'post', + maxBodyLength: Infinity, + url: "https://hist.databento.com/v0/timeseries.get_range", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64') + }, + data: data + }; + + let responseBack + axios.request(config) + .then((response) => { + //console.log("\n -> Resp " + response.data) + responseBack = response.data + res.status(200).send(responseBack); + }) + .catch((error) => { + console.log(error); + res.status(500).send({ error: error }); + }); + + } catch (error) { + console.error(error); + res.status(500).send({ error: error }); + } + }) +}; + +/**************************** END APIs ****************************/ + +const startIndex = async () => { + + const startServer = async () => { + console.log("\nSTARTING NODEJS SERVER") + return new Promise(async (resolve, reject) => { + server = app.listen(port, function () { + console.log(' -> TradeNote server started on http://localhost:' + port) + }); + resolve(server) + }) + } + + const runServer = async () => { + console.log("\nRUNNING SERVER"); + + return new Promise(async (resolve, reject) => { + if (process.env.NODE_ENV == 'dev') { + // Set up proxy for development environment + const proxy = new Proxy.createProxyServer({ + target: { host: 'localhost', port: PROXY_PORT }, + }); + + // Middleware to handle API routes first (do not pass to Vite) + app.use('/api/*', (req, res, next) => { + // Handle API routes here + //console.log("Handling API route:", req.url); + next(); // Continue processing the request for /api/* routes + }); + + // Set up API routes for dev mode as well + setupApiRoutes(app); + + // Proxy all other routes (non-API) to Vite + app.use((req, res, next) => { + if (req.url.startsWith('/api/')) { + return next(); // Let the /api/* routes be handled by the previous middleware + } + proxy.web(req, res); // Proxy all other routes to Vite + }); + + // Start Vite dev server + const vite = await Vite.createServer({ server: { port: PROXY_PORT } }); + vite.listen(); + console.log(" -> Running vite dev server"); + resolve(); + } else { + // In production, handle API routes normally + app.use('/api/*', express.json(), (req, res, next) => { + //console.log(`Received API request: ${req.method} ${req.url}`); + next(); // Pass control to specific API handlers + }); + + // Set up API routes for production + setupApiRoutes(app); + + // Serve static files from 'dist' folder + app.use(express.static('dist')); + + // Fallback for SPA + app.get('*', (req, res) => { + res.sendFile(path.resolve('dist', 'index.html')); + }); + console.log(" -> Running prod server"); + resolve(); + } + }); + }; + + + const setupParseServer = async () => { + console.log("\nSTARTING PARSE SERVER") + return new Promise(async (resolve, reject) => { + const serv = new ParseServer({ + databaseURI: databaseURI, + appId: process.env.APP_ID, + masterKey: process.env.MASTER_KEY, + port: port, + masterKeyIps: ['0.0.0.0/0', '::/0'], + allowClientClassCreation: false, + allowExpiredAuthDataToken: false + }); + + // EXPRESS USE + await serv.start().then(() => { + app.use('/parse', serv.app); + console.log(" -> ParseNode server started") + resolve() + }) + }) + } + + await startServer() + await setupParseServer() + await runServer() + + /*var parseDashboard = new ParseDashboard({ + "apps": [{ + "serverURL": "/parse", + "appId": process.env.APP_ID, + "masterKey": process.env.MASTER_KEY, + "appName": "TradeNote" + }], + "trustProxy": true + });*/ + + + + if (process.env.PARSE_DASHBOARD) app.use('/parseDashboard', parseDashboard) + + //INIT + //console.log("\nInitializing ParseNode") + ParseNode.initialize(process.env.APP_ID) + ParseNode.serverURL = "http://localhost:" + port + "/parse" + ParseNode.masterKey = process.env.MASTER_KEY + +} +startIndex() diff --git a/src/utils/screenshots.js b/src/utils/screenshots.js index 05ba1c2a..30a88346 100644 --- a/src/utils/screenshots.js +++ b/src/utils/screenshots.js @@ -192,6 +192,7 @@ export async function useSetupImageUpload(event, param1, param2, param3, tradeId screenshot.symbol = param2 screenshot.side = param3 screenshot.tradeId = tradeId + screenshot.objectId = undefined } const file = event.target.files[0]; From 022c01e704a5a260bce6395d4bd3e2a8ea813c9a Mon Sep 17 00:00:00 2001 From: mx00 <2784107+mx00@users.noreply.github.com> Date: Sun, 24 May 2026 23:54:52 -0700 Subject: [PATCH 5/6] Support multiple screenshots per trade modal --- index.mjs.bak | 570 -------------------------------------------- src/views/Daily.vue | 16 +- 2 files changed, 10 insertions(+), 576 deletions(-) delete mode 100644 index.mjs.bak diff --git a/index.mjs.bak b/index.mjs.bak deleted file mode 100644 index 5c49cfd9..00000000 --- a/index.mjs.bak +++ /dev/null @@ -1,570 +0,0 @@ -import express from 'express'; -import { ParseServer } from 'parse-server' -//var ParseDashboard = require('parse-dashboard'); -import ParseNode from 'parse/node.js' -import path from 'path' -import fs from 'fs' -import axios from 'axios' -import * as Vite from 'vite' -import { MongoClient } from "mongodb" -import Proxy from 'http-proxy' -import { useImportTrades, useGetExistingTradesArray, useUploadTrades } from './src/utils/addTrades.js'; -import { currentUser, uploadMfePrices } from './src/stores/globals.js'; -import { useGetTimeZone } from './src/utils/utils.js'; -import Stripe from 'stripe'; - - -/* STRIPE VAR */ -let stripeSk //secret key -let stripePk // public key -let stripePriceId -let stripeTrialPeriod - -if (process.env.STRIPE_SK) { - stripeSk = new Stripe(process.env.STRIPE_SK); - stripePk = process.env.STRIPE_PK - stripePriceId = process.env.STRIPE_PRICE_ID - stripeTrialPeriod = process.env.STRIPE_TRIAL_PERIOD -} - -/* END STRIPE */ - -let databaseURI - -if (process.env.MONGO_URI) { - databaseURI = process.env.MONGO_URI -} else if (process.env.MONGO_ATLAS) { - databaseURI = "mongodb+srv://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin" -} else { - databaseURI = "mongodb://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + ":" + process.env.MONGO_PORT + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin" -} - -console.log("\nCONNECTING TO MONGODB") -let hiddenDatabaseURI = databaseURI.replace(/:\/\/[^@]*@/, "://***@") -console.log(' -> Database URI ' + hiddenDatabaseURI) - -let tradenoteDatabase = process.env.TRADENOTE_DATABASE - -var app = express(); -app.use(express.json()); - -const port = process.env.TRADENOTE_PORT; -const PROXY_PORT = 39482; - -// SERVER - -let server = null - -export let allowRegister = false - - -/**************************** APIs ****************************/ - -const setupApiRoutes = (app) => { - - app.post("/api/parseAppId", (req, res) => { - //console.log("\nAPI : post APP ID") - //console.log(process.env.APP_ID) - res.send(process.env.APP_ID) - }); - - app.post("/api/registerPage", (req, res) => { - //console.log("\nAPI : post APP ID") - //console.log(" REGISTER_OFF "+process.env.REGISTER_OFF) - res.send(process.env.REGISTER_OFF) - }); - - app.post("/api/posthog", (req, res) => { - //console.log("\nAPI : posthog") - if (process.env.ANALYTICS_OFF) { - res.send("off") - } else { - res.send("phc_FxkjH1O898jKu0yiELC3aWKda3vGov7waGN0weU5kw0") - } - }); - - - /********************************************** - * CLOUD / STRIPE - **********************************************/ - - app.post("/api/checkCloudPayment", async (req, res) => { - // Used for checking if can access add*, in case it's a paying user - let currentUser = req.body.currentUser - //console.log(" currentUser "+JSON.stringify(currentUser)) - //console.log(" current user " + JSON.stringify(req.body.currentUser)) - if (process.env.STRIPE_SK) { - //console.log("\nAPI : checkCloudPayment") - // Check if user is stripe customer - - // Check if user has paying customer - if (currentUser.hasOwnProperty("paymentService") && currentUser.paymentService.hasOwnProperty("subscriptionId")) { - /// if yes, let inn, status 200 - const activeSubscription = ['active', 'trialing', 'past_due'] - const subscription = await stripeSk.subscriptions.retrieve(currentUser.paymentService.subscriptionId) - if (activeSubscription.includes(subscription.status)) { - console.log(" -> User has valid subscription."); - res.status(200).send('OK'); - } else { - console.log(" -> User has invalid subscription."); - res.status(403).send('Forbidden'); - } - - } - - /// If not, check if user is within trial period - else { - - // Convert createdAt to a Date object - const createdAtDate = new Date(currentUser.createdAt); - - // Get the current time - const currentDate = new Date(); - - // Calculate the time difference in milliseconds - const timeDifference = currentDate - createdAtDate; - - // Convert the time difference to days - const differenceInDays = timeDifference / (1000 * 60 * 60 * 24); // Milliseconds to days - - //// if older, redirect to stripe / status 403 - if (differenceInDays > stripeTrialPeriod) { - console.log(" -> User is past trial period."); - res.status(403).send('Forbidden'); - } - - //// else, let inn, status 200 - else { - console.log(" -> User is within trial period."); - res.status(200).send('OK'); - } - } - - - - - } else { - res.status(200).send('OK'); - } - }); - - app.post('/api/create-checkout-session', async (req, res) => { - - let return_url - if (process.env.NODE_ENV == 'dev') { - return_url = `http://localhost:${port}/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}` - }else{ - return_url = `https://app.tradenote.co/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}` - } - - //console.log(" -> return_url : "+return_url) - const session = await stripeSk.checkout.sessions.create({ - ui_mode: 'embedded', - line_items: [ - { - // Provide the exact Price ID (for example, pr_1234) of the product you want to sell - price: stripePriceId, - quantity: 1, - }, - ], - mode: 'subscription', - return_url: return_url, - automatic_tax: { enabled: true }, - }); - - res.send({ clientSecret: session.client_secret }); - }); - - app.get('/api/getStripePk', async (req, res) => { - res.status(200).send(stripePk); - }) - - app.get('/api/session-status', async (req, res) => { - try { - console.log("Getting session status"); - const session = await stripeSk.checkout.sessions.retrieve(req.query.session_id); - - //console.log("Session retrieved:", JSON.stringify(session)); - - res.send({ - session: session, - status: session.status, - customer_email: session.customer_details.email, - customer_id: session.customer - }); - } catch (error) { - console.error("Error retrieving session:", error.message); - res.status(500).send({ error: error.message }); - } - }); - - /******************* END CLOUD ****************************/ - - app.get('/api/dockerVersion', async (req, res) => { - console.log("Getting Docker Version"); - await axios.get("https://hub.docker.com/v2/namespaces/eleventrading/repositories/tradenote/tags") - .then((response) => { - //console.log(" -> data " + JSON.stringify(response.data)) - res.status(200).send(response.data); - }) - .catch((error) => { - res.status(500).send({ error: error.message }); - }) - .finally(function () { - // always executed - }) - }); - - app.post("/api/updateSchemas", async (req, res) => { - - if (!process.env.STRIPE_SK || process.env.UPSERT_SCHEMA) { - console.log("\nAPI : post update schema") - - let rawdata = fs.readFileSync('requiredClasses.json'); - let schemasJson = JSON.parse(rawdata); - //console.log("schemasJson "+JSON.stringify(schemasJson)) - - let existingSchema = [] - const getExistingSchema = await ParseNode.Schema.all() - //console.log(" -> Get existing schema " + JSON.stringify(getExistingSchema)) - - /* 1- Rename legacy names in mongoDB */ - const renameMongoDb = (param1, param2) => { - return new Promise(async (resolve, reject) => { - console.log(" -> Renaming class " + param1 + " to " + param2) - MongoClient.connect(databaseURI).then(async (client) => { - console.log(" --> Connected to MongoDB") - const connect = client.db(tradenoteDatabase); - const allCollections = await connect.listCollections().toArray() - //console.log("allCollections "+JSON.stringify(allCollections)) - let collectionExists = allCollections.filter(obj => obj.name == param1) - //console.log(" --> collectionExists "+collectionExists.length) - if (collectionExists.length > 0) { - const collection = connect.collection(param1); - collection.rename(param2).then(() => { - console.log(" -> Renamed class successfully"); - resolve() - }) - } else { - console.log(" -> Collection doesn't exist.") - resolve() - } - - }).catch((err) => { - console.log(" -> Error renaming MongoDB class: " + err.Message); - reject() - }) - }) - } - - for (let i = 0; i < getExistingSchema.length; i++) { - //console.log("Class name " + getExistingSchema[i].className) - - //we check for classes/collections that need to be renamed - if (getExistingSchema[i].className == "setupsEntries" || getExistingSchema[i].className == "journals") { - let oldName = getExistingSchema[i].className - let newName - - if (getExistingSchema[i].className == "setupsEntries") newName = "screenshots" - if (getExistingSchema[i].className == "journals") newName = "diaries" - if (getExistingSchema[i].className == "patternsMistakes") newName = "setups" - - await renameMongoDb(oldName, newName) - } else { - existingSchema.push(getExistingSchema[i].className) - } - } - //console.log(" -> Existing Schema " + existingSchema) - - /* 2- Update or save new schemas in mongoDB */ - const updateSaveSchema = (param1, param2, param3) => { - return new Promise((resolve, reject) => { - const mySchema = new ParseNode.Schema(param1); - if (param2[param3].type === "String") mySchema.addString(param3) - if (param2[param3].type === "Number") mySchema.addNumber(param3) - if (param2[param3].type === "Boolean") mySchema.addBoolean(param3) - if (param2[param3].type === "Date") mySchema.addDate(param3) - if (param2[param3].type === "File") mySchema.addFile(param3) - if (param2[param3].type === "GeoPoint") mySchema.addGeoPoint(param3) - if (param2[param3].type === "Polygon") mySchema.addPolygon(param3) - if (param2[param3].type === "Array") mySchema.addArray(param3) - if (param2[param3].type === "Object") mySchema.addObject(param3) - if (param2[param3].type === "Pointer") mySchema.addPointer(param3, param2[param3].targetClass) - if (param2[param3].type === "Relation") mySchema.addRelation(param3, param2[param3].targetClass) - - //console.log("existing schema "+existingSchema) - //console.log("includes ? "+existingSchema.includes(className)) - - //If ParseNode (existing) schema includes the class name from required classes then update (just in case). Else add, and then add that class to existing schema array - if (existingSchema.includes(param1)) { - mySchema.update().then((result) => { - console.log(" --> Updating field " + param3) - //console.log(" -> Updated schema " + JSON.stringify(result)) - resolve() - }) - } else { - mySchema.save().then((result) => { - //console.log(" -> Save new schema " + JSON.stringify(result)) - console.log(" --> Saving field " + param3) - existingSchema.push(param1) // Once saved, we update for the rest of the fields, so we need to push to existingSchema - //console.log(" -> Existing Schema " + existingSchema) - resolve() - }) - } - }) - } - - for (let i = 0; i < schemasJson.length; i++) { - //console.log("el " + schemasJson[i].className) - let className = schemasJson[i].className - console.log(" -> Upsert class/collection " + className + " in ParseNode Schema") - let obj = schemasJson[i].fields - for (const key of Object.keys(obj)) { - //console.log(key, obj[key]); - if (key != "objectId" && key != "updatedAt" && key != "createdAt" && key != "ACL") { - //console.log(" -> Key " + key) - await updateSaveSchema(className, obj, key) - } - - } - } - - res.send({ "existingSchema": existingSchema }) - } else { - res.status(200).send('OK'); - } - - }) - - - - - app.use(express.json()); - - let allUsers - const getAllUsers = async () => { - console.log(" -> Getting all users") - return new Promise(async (resolve, reject) => { - const parseObject = ParseNode.Object.extend("_User"); - const query = new ParseNode.Query(parseObject); - const results = await query.find({ useMasterKey: true }); - allUsers = JSON.parse(JSON.stringify(results)) - resolve() - }) - } - - const validateApiKey = async (req, res, next) => { - await getAllUsers() - const targetKey = req.headers['api-key'] || req.query['api-key']; - //console.log(" -> target Key " + targetKey) - - const checkIPKey = (allUsers, targetKey) => { - for (const user of Object.values(allUsers)) { - if (user.hasOwnProperty("apis")) { - const index = user.apis.findIndex(obj => obj.key === targetKey); - if (index !== -1) { - currentUser.value = user - return true; - } - } - - } - return -1; // Return -1 if not found - } - - // Usage example - const hasIPKey = checkIPKey(allUsers, targetKey); - - if (hasIPKey) { - console.log(" -> Valid api key found :)") - next(); - } else { - console.log(" -> Invalid api key") - return res.status(401).send({ error: 'Invalid API key' }); - } - } - - app.post('/api/trades', validateApiKey, async (req, res) => { - const data = req.body; - try { - if (data && !data.data.length > 0) { - res.status(200).send(" -> No trades to import"); - } - else { - - uploadMfePrices.value = data.uploadMfePrices - - //console.log(" uploadMfePrices "+uploadMfePrices.value) - // Call the function from addTrades.js - - await useGetTimeZone() - await useGetExistingTradesArray("api", ParseNode) - await useImportTrades(data.data, "api", data.selectedBroker, ParseNode) - await useUploadTrades("api", ParseNode) - - res.status(200).send(" -> Saved Trades to ParseNode DB"); - } - } catch (error) { - console.error(error); - res.status(500).send({ error: 'Error creating executions' }); - } - }); - - app.post('/api/databento', async (req, res) => { - //console.log(" calling databento") - const data = req.body; - //console.log(" data "+JSON.stringify(data)) - try { - const username = data.username; - const password = ''; - - - let config = { - method: 'post', - maxBodyLength: Infinity, - url: "https://hist.databento.com/v0/timeseries.get_range", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64') - }, - data: data - }; - - let responseBack - axios.request(config) - .then((response) => { - //console.log("\n -> Resp " + response.data) - responseBack = response.data - res.status(200).send(responseBack); - }) - .catch((error) => { - console.log(error); - res.status(500).send({ error: error }); - }); - - } catch (error) { - console.error(error); - res.status(500).send({ error: error }); - } - }) -}; - -/**************************** END APIs ****************************/ - -const startIndex = async () => { - - const startServer = async () => { - console.log("\nSTARTING NODEJS SERVER") - return new Promise(async (resolve, reject) => { - server = app.listen(port, function () { - console.log(' -> TradeNote server started on http://localhost:' + port) - }); - resolve(server) - }) - } - - const runServer = async () => { - console.log("\nRUNNING SERVER"); - - return new Promise(async (resolve, reject) => { - if (process.env.NODE_ENV == 'dev') { - // Set up proxy for development environment - const proxy = new Proxy.createProxyServer({ - target: { host: 'localhost', port: PROXY_PORT }, - }); - - // Middleware to handle API routes first (do not pass to Vite) - app.use('/api/*', (req, res, next) => { - // Handle API routes here - //console.log("Handling API route:", req.url); - next(); // Continue processing the request for /api/* routes - }); - - // Set up API routes for dev mode as well - setupApiRoutes(app); - - // Proxy all other routes (non-API) to Vite - app.use((req, res, next) => { - if (req.url.startsWith('/api/')) { - return next(); // Let the /api/* routes be handled by the previous middleware - } - proxy.web(req, res); // Proxy all other routes to Vite - }); - - // Start Vite dev server - const vite = await Vite.createServer({ server: { port: PROXY_PORT } }); - vite.listen(); - console.log(" -> Running vite dev server"); - resolve(); - } else { - // In production, handle API routes normally - app.use('/api/*', express.json(), (req, res, next) => { - //console.log(`Received API request: ${req.method} ${req.url}`); - next(); // Pass control to specific API handlers - }); - - // Set up API routes for production - setupApiRoutes(app); - - // Serve static files from 'dist' folder - app.use(express.static('dist')); - - // Fallback for SPA - app.get('*', (req, res) => { - res.sendFile(path.resolve('dist', 'index.html')); - }); - console.log(" -> Running prod server"); - resolve(); - } - }); - }; - - - const setupParseServer = async () => { - console.log("\nSTARTING PARSE SERVER") - return new Promise(async (resolve, reject) => { - const serv = new ParseServer({ - databaseURI: databaseURI, - appId: process.env.APP_ID, - masterKey: process.env.MASTER_KEY, - port: port, - masterKeyIps: ['0.0.0.0/0', '::/0'], - allowClientClassCreation: false, - allowExpiredAuthDataToken: false - }); - - // EXPRESS USE - await serv.start().then(() => { - app.use('/parse', serv.app); - console.log(" -> ParseNode server started") - resolve() - }) - }) - } - - await startServer() - await setupParseServer() - await runServer() - - /*var parseDashboard = new ParseDashboard({ - "apps": [{ - "serverURL": "/parse", - "appId": process.env.APP_ID, - "masterKey": process.env.MASTER_KEY, - "appName": "TradeNote" - }], - "trustProxy": true - });*/ - - - - if (process.env.PARSE_DASHBOARD) app.use('/parseDashboard', parseDashboard) - - //INIT - //console.log("\nInitializing ParseNode") - ParseNode.initialize(process.env.APP_ID) - ParseNode.serverURL = "http://localhost:" + port + "/parse" - ParseNode.masterKey = process.env.MASTER_KEY - -} -startIndex() diff --git a/src/views/Daily.vue b/src/views/Daily.vue index 12bd240e..3b1195ea 100644 --- a/src/views/Daily.vue +++ b/src/views/Daily.vue @@ -212,19 +212,21 @@ async function clickTradesModal(param1, param2, param3) { await Promise.all([resetExcursion(), useResetTags()]) //For setups I have added setups into filteredTrades. For screenshots and excursions I need to find so I create on each modal page a screenshot and excursion object - let findScreenshot = screenshots.find(obj => obj.name == filteredTradeId) + let findScreenshots = screenshots.filter(obj => obj.name == filteredTradeId) for (let key in screenshot) delete screenshot[key] candlestickChartFailureMessage.value = null // to avoid message when screenshot is present - if (findScreenshot) { + if (findScreenshots.length > 0) { //console.log(" found screenshot") - for (let key in findScreenshot) { - screenshot[key] = findScreenshot[key] + screenshot.multiple = findScreenshots + for (let key in findScreenshots[0]) { + screenshot[key] = findScreenshots[0][key] } } else { //console.log(" did not find any screenshot") screenshot.side = null screenshot.type = null + screenshot.multiple = [] /* GET OHLC / CANDLESTICK CHARTS */ @@ -1190,8 +1192,10 @@ function getOHLC(date, symbol, type) {