Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,12 @@ sketch

### react frontend ###
frontend/build
frontend/.env.local
frontend/src/conf/config.js

### backend build ###
backend/build

# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,react


Expand Down
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
FROM node:14-alpine3.16

RUN npm install pm2
FROM node:20.20.1-alpine3.23

WORKDIR /src

Expand Down
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.PHONY: build-node clear build-docker test-docker help

# Colors for output
BLUE := \033[0;34m
GREEN := \033[0;32m
YELLOW := \033[0;33m
NC := \033[0m # No Color

help:
@echo "$(BLUE)Age Viewer Makefile Commands$(NC)"
@echo ""
@echo "$(GREEN)build-node$(NC) - Build Node.js frontend and backend"
@echo "$(GREEN)clear$(NC) - Clean up build artifacts, node_modules, and containers"
@echo "$(GREEN)build-docker$(NC) - Build Docker image"
@echo "$(GREEN)test-docker$(NC) - Build and run Docker container for testing"
@echo "$(GREEN)help$(NC) - Show this help message"

build-node:
@echo "$(YELLOW)Building Node.js projects...$(NC)"
npm run build-front
npm run build-back
@echo "$(GREEN)✅ Node.js build complete$(NC)"

clear:
@echo "$(YELLOW)Cleaning up...$(NC)"
rm -rf frontend/build
rm -rf backend/build
rm -rf frontend/node_modules
rm -rf backend/node_modules
find . -type d -name ".next" -exec rm -rf {} + 2>/dev/null || true
@echo "$(GREEN)✅ Cleanup complete$(NC)"

build-docker:
@echo "$(YELLOW)Building Docker image...$(NC)"
docker build -t age-viewer:latest .
@echo "$(GREEN)✅ Docker image built: age-viewer:latest$(NC)"
@echo ""
@echo "$(BLUE)Image details:$(NC)"
docker images age-viewer:latest

test-docker: build-docker
@echo "$(YELLOW)Starting Docker container...$(NC)"
docker run -it --rm \
-p 3000:3000 \
-p 3001:3001 \
--name age-viewer-test \
-e NODE_ENV=production \
age-viewer:latest
@echo "$(GREEN)Container stopped$(NC)"

clean-containers:
@echo "$(YELLOW)Removing age-viewer containers...$(NC)"
docker ps -a | grep age-viewer | awk '{print $$1}' | xargs docker rm -f 2>/dev/null || true
@echo "$(GREEN)✅ Containers cleaned$(NC)"

clean-images:
@echo "$(YELLOW)Removing age-viewer images...$(NC)"
docker images | grep age-viewer | awk '{print $$3}' | xargs docker rmi -f 2>/dev/null || true
@echo "$(GREEN)✅ Images cleaned$(NC)"
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"start:production": "node ./build/bin/www.js"
},
"dependencies": {
"@babel/runtime": "^7.28.6",
"antlr4": "4.9.3",
"chai": "^4.3.7",
"cookie-parser": "~1.4.5",
Expand Down
4 changes: 3 additions & 1 deletion backend/src/common/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

export function wrap(asyncFn) {
function wrap(asyncFn) {
return (async (req, res, next) => {
try {
return await asyncFn(req, res, next)
Expand All @@ -26,3 +26,5 @@ export function wrap(asyncFn) {
}
})
}

module.exports = { wrap };
4 changes: 2 additions & 2 deletions backend/src/config/Flavors.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
* under the License.
*/

export default {
module.exports = {
AGE: 'AGE'
}
};
4 changes: 2 additions & 2 deletions backend/src/config/Pg.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

export default {
module.exports = {
// all valid client config options are also valid here
// in addition here are the pool specific configuration parameters:
// number of milliseconds to wait before timing out when connecting a new client
Expand All @@ -30,4 +30,4 @@ export default {
// maximum number of clients the pool should contain
// by default this is set to 10.
max: 10,
}
};
11 changes: 5 additions & 6 deletions backend/src/models/GraphRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import PgConfig from '../config/Pg'

import pg from 'pg';
import types from 'pg-types';
import {setAGETypes, onConnectQueries} from '../tools/AGEParser';
import { getQuery } from '../tools/SQLFlavorManager';
const PgConfig = require('../config/Pg');
const pg = require('pg');
const types = require('pg-types');
const {setAGETypes, onConnectQueries} = require('../tools/AGEParser');
const { getQuery } = require('../tools/SQLFlavorManager');


class GraphRepository {
Expand Down
8 changes: 3 additions & 5 deletions backend/src/services/databaseService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@
* under the License.
*/

import { getQuery } from "../tools/SQLFlavorManager";
import * as util from "util";
import GraphRepository from '../models/GraphRepository';
import { start } from "repl";
import { get } from "http";
const { getQuery } = require("../tools/SQLFlavorManager");
const util = require("util");
const GraphRepository = require('../models/GraphRepository');

class DatabaseService {
constructor() {
Expand Down
11 changes: 5 additions & 6 deletions backend/src/tools/AGEParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
* under the License.
*/

import antlr4 from 'antlr4';
import { cli } from 'winston/lib/winston/config';
import AgtypeLexer from './AgtypeLexer';
import AgtypeParser from './AgtypeParser';
import CustomAgTypeListener from './CustomAgTypeListener';
const antlr4 = require('antlr4');
const AgtypeLexer = require('./AgtypeLexer');
const AgtypeParser = require('./AgtypeParser');
const CustomAgTypeListener = require('./CustomAgTypeListener');

function AGTypeParse(input) {
const chars = new antlr4.InputStream(input);
Expand Down Expand Up @@ -58,4 +57,4 @@ async function onConnectQueries(client){
return {server_version: v.rows[0].server_version};
}

export {setAGETypes, AGTypeParse, onConnectQueries}
module.exports = {setAGETypes, AGTypeParse, onConnectQueries};
6 changes: 3 additions & 3 deletions backend/src/tools/AgtypeLexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

// Generated from src/tools/Agtype.g4 by ANTLR 4.9.2
// jshint ignore: start
import antlr4 from 'antlr4';
const antlr4 = require('antlr4');


const serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786",
Expand Down Expand Up @@ -146,7 +146,7 @@ const atn = new antlr4.atn.ATNDeserializer().deserialize(serializedATN);

const decisionsToDFA = atn.decisionToState.map((ds, index) => new antlr4.dfa.DFA(ds, index));

export default class AgtypeLexer extends antlr4.Lexer {
class AgtypeLexer extends antlr4.Lexer {

static grammarFileName = "Agtype.g4";
static channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"];
Expand Down Expand Up @@ -194,5 +194,5 @@ AgtypeLexer.RegularFloat = 17;
AgtypeLexer.ExponentFloat = 18;
AgtypeLexer.WS = 19;


module.exports = AgtypeLexer;

6 changes: 4 additions & 2 deletions backend/src/tools/AgtypeListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

// Generated from src/tools/Agtype.g4 by ANTLR 4.9.2
// jshint ignore: start
import antlr4 from 'antlr4';
const antlr4 = require('antlr4');

// This class defines a complete listener for a parse tree produced by AgtypeParser.
export default class AgtypeListener extends antlr4.tree.ParseTreeListener {
class AgtypeListener extends antlr4.tree.ParseTreeListener {

// Enter a parse tree produced by AgtypeParser#agType.
enterAgType(ctx) {
Expand Down Expand Up @@ -159,3 +159,5 @@ export default class AgtypeListener extends antlr4.tree.ParseTreeListener {
}

}

module.exports = AgtypeListener;
8 changes: 5 additions & 3 deletions backend/src/tools/AgtypeParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

// Generated from src/tools/Agtype.g4 by ANTLR 4.9.2
// jshint ignore: start
import antlr4 from 'antlr4';
import AgtypeListener from './AgtypeListener.js';
const antlr4 = require('antlr4');
const AgtypeListener = require('./AgtypeListener.js');

const serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786",
"\u5964\u0003\u0015R\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004",
Expand Down Expand Up @@ -80,7 +80,7 @@ const decisionsToDFA = atn.decisionToState.map((ds, index) => new antlr4.dfa.DFA

const sharedContextCache = new antlr4.PredictionContextCache();

export default class AgtypeParser extends antlr4.Parser {
class AgtypeParser extends antlr4.Parser {

static grammarFileName = "Agtype.g4";
static literalNames = [null, "'true'", "'false'", "'null'", "'{'",
Expand Down Expand Up @@ -998,3 +998,5 @@ AgtypeParser.PairContext = PairContext;
AgtypeParser.ArrayContext = ArrayContext;
AgtypeParser.TypeAnnotationContext = TypeAnnotationContext;
AgtypeParser.FloatLiteralContext = FloatLiteralContext;

module.exports = AgtypeParser;
4 changes: 2 additions & 2 deletions backend/src/tools/CustomAgTypeListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import AgtypeListener from "./AgtypeListener";
const AgtypeListener = require("./AgtypeListener");

class CustomAgTypeListener extends AgtypeListener {
rootObject = null;
Expand Down Expand Up @@ -159,4 +159,4 @@ class CustomAgTypeListener extends AgtypeListener {
}
}

export default CustomAgTypeListener;
module.exports = CustomAgTypeListener;
32 changes: 28 additions & 4 deletions backend/src/tools/SQLFlavorManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,42 @@
* specific language governing permissions and limitations
* under the License.
*/
import * as path from "path";
import fs from 'fs'
const path = require("path");
const fs = require('fs');

const sqlBasePath = path.join(__dirname, '../../sql');

// todo: util.format -> ejs
function getQuery(name, version='') {
const sqlPath = path.join(sqlBasePath, version, `${name}.sql`);
let sqlPath = path.join(sqlBasePath, version, `${name}.sql`);

// If version-specific file doesn't exist, try fallback versions
if (version && !fs.existsSync(sqlPath)) {
const versionNum = parseInt(version, 10);
// For versions >= 16, use version 15 (compatible)
if (versionNum >= 16) {
sqlPath = path.join(sqlBasePath, '15', `${name}.sql`);
} else if (versionNum >= 11) {
// For versions 11-15, try each down to 11
for (let v = versionNum; v >= 11; v--) {
const fallbackPath = path.join(sqlBasePath, v.toString(), `${name}.sql`);
if (fs.existsSync(fallbackPath)) {
sqlPath = fallbackPath;
break;
}
}
}
}

// Final fallback: check base sql directory
if (!fs.existsSync(sqlPath)) {
sqlPath = path.join(sqlBasePath, `${name}.sql`);
}

if (!fs.existsSync(sqlPath)) {
throw new Error(`SQL does not exist, name = ${name}`);
}
return fs.readFileSync(sqlPath, 'utf8');
}

export {getQuery}
module.exports = { getQuery };
15 changes: 11 additions & 4 deletions backend/src/util/JsonBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
* under the License.
*/

export function stringWrap(valstr, flavor) {
function stringWrap(valstr, flavor) {
return JSON.stringify(valstr);
}

export function JsonStringify(flavor, record) {
function JsonStringify(flavor, record) {
let ageJsonStr = '{';
let isFirst = true;
for (const [key, value] of Object.entries(record)) {
Expand All @@ -36,7 +36,7 @@ export function JsonStringify(flavor, record) {
return ageJsonStr;
}

export async function createVertex(client, graphPathStr, label, record, flavor) {
async function createVertex(client, graphPathStr, label, record, flavor) {
const createQ = `CREATE (n:${label} ${JsonStringify(flavor, record)})`;
if (flavor === 'AGE') {
return AGECreateVertex(client, graphPathStr, createQ);
Expand All @@ -51,7 +51,7 @@ async function AGECreateVertex(client, graphPathStr, createQ) {
from cypher('${graphPathStr}', $$ ${createQ} $$) as (a agtype)`);
}

export async function createEdge(client, label, record, graphPathStr, edgeStartLabel, edgeEndLabel, startNodeName, endNodeName, flavor) {
async function createEdge(client, label, record, graphPathStr, edgeStartLabel, edgeEndLabel, startNodeName, endNodeName, flavor) {
const createQ = `CREATE (:${edgeStartLabel} {name: ${stringWrap(startNodeName, flavor)}})-[n:${label} ${JsonStringify(flavor, record)}]->(:${edgeEndLabel} {name: ${stringWrap(endNodeName, flavor)}})`;
if (flavor === 'AGE') {
return AGECreateEdge(client, graphPathStr, createQ);
Expand All @@ -65,3 +65,10 @@ async function AGECreateEdge(client, graphPathStr, createQ) {
`select *
from cypher('${graphPathStr}', $$ ${createQ} $$) as (a agtype)`);
}

module.exports = {
stringWrap,
JsonStringify,
createVertex,
createEdge
};
Loading