Skip to content

Commit 802a91f

Browse files
Merge pull request #51 from algosup/dev
Merge dev to main
2 parents eb370d9 + 68cf59d commit 802a91f

33 files changed

+3323
-1
lines changed

.github/workflows/CI.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# This is a basic workflow to help you get started with Actions
2+
3+
name: CI
4+
5+
# Controls when the workflow will run
6+
on:
7+
# Triggers the workflow on push or pull request events but only for the "main" branch
8+
push:
9+
branches: [ "dev" ]
10+
pull_request:
11+
branches: [ "main" ]
12+
paths:
13+
- 'frontend/**'
14+
- 'backend/**'
15+
16+
# Allows you to run this workflow manually from the Actions tab
17+
workflow_dispatch:
18+
19+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
20+
jobs:
21+
# This workflow contains a single job called "build"
22+
build:
23+
# The type of runner that the job will run on
24+
runs-on: ubuntu-latest
25+
26+
# Steps represent a sequence of tasks that will be executed as part of the job
27+
steps:
28+
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
29+
- uses: actions/checkout@v4
30+
31+
# Set up Node.js environment
32+
- name: Set up Node.js
33+
uses: actions/setup-node@v3
34+
with:
35+
node-version: '23.6.0' # You can specify the Node.js version you need
36+
37+
# Runs a single command using the runners shell
38+
- name: CI starting
39+
run: |
40+
echo ⏳ CI testing begin...
41+
42+
- name: Install Dependencies
43+
run: |
44+
echo ⏳ install frontend dependencies...
45+
cd ./frontend/
46+
npm install
47+
echo ✅ frontend dependencies installed!
48+
echo ⏳ install backend dependencies...
49+
cd ../backend/
50+
npm install
51+
echo ✅ backend dependencies installed!
52+
53+
- name: Building
54+
run: |
55+
echo ⏳ Building...
56+
cd ./frontend/
57+
npm run build
58+
echo ✅ Built!
59+
60+
frontend-testing:
61+
62+
runs-on: ubuntu-latest
63+
64+
steps:
65+
66+
- uses: actions/checkout@v4
67+
68+
# Deploy Render app
69+
- name: Deploy Application
70+
env:
71+
deploy_url: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}
72+
run: |
73+
echo ⏳ Frontend testing is setting up...
74+
if [[ -z "$deploy_url" ]]; then
75+
echo "❌ Error: deploy_url is not set!"
76+
exit 1
77+
fi
78+
echo "⏳ Triggering deployment..."
79+
curl -v "$deploy_url"
80+
sleep 60
81+
echo ✅ Application deployed!
82+
83+
- name: Run e2e testing
84+
run: |
85+
echo ⏳ frontend e2e testing begin...
86+
cd ./frontend/
87+
npm i
88+
npm run test:e2e
89+
echo ✅ frontend e2e testing ended!
90+
91+
backend-testing:
92+
93+
runs-on: ubuntu-latest
94+
95+
steps:
96+
97+
- uses: actions/checkout@v4
98+
99+
- name: Run API e2e testing
100+
run: |
101+
echo ⏳ API testing begin...
102+
cd ./backend/
103+
npm i
104+
npm run test:e2e
105+
echo ✅ backend e2e testing ended!

.gitignore

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,31 @@
11
.DS_Store
2-
.vscode/
2+
.vscode/
3+
# Logs
4+
logs
5+
*.log
6+
npm-debug.log*
7+
yarn-debug.log*
8+
yarn-error.log*
9+
pnpm-debug.log*
10+
lerna-debug.log*
11+
12+
node_modules
13+
temp_user_data
14+
package-lock.json
15+
dist
16+
dist-ssr
17+
*.local
18+
19+
# Editor directories and files
20+
.vscode/*
21+
!.vscode/extensions.json
22+
.idea
23+
.DS_Store
24+
*.suo
25+
*.ntvs*
26+
*.njsproj
27+
*.sln
28+
*.sw?
29+
30+
# API files
31+
parsed_files

backend/index.js

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import express from 'express';
2+
import multer from 'multer';
3+
import { fileURLToPath } from 'url';
4+
import { dirname, join, extname } from 'path';
5+
import { promises as fs } from 'fs';
6+
import cors from 'cors';
7+
8+
import { parseSDF } from './src/sdfProcess.js';
9+
import { parseVerilog } from './src/vProcess.js';
10+
import { mergeJsonForD3 } from './src/mergeVerilogSdf.js';
11+
12+
export const app = express();
13+
const PORT = 3001;
14+
15+
// Get absolute path
16+
const __filename = fileURLToPath(import.meta.url);
17+
const __dirname = dirname(__filename);
18+
19+
// Authorize CORS
20+
app.use(cors());
21+
app.use(express.json());
22+
23+
// Configure multer for handling SDF file upload
24+
// Store file in memory, not on disk
25+
const storage = multer.memoryStorage();
26+
27+
// Filter to verify file type
28+
const upload = multer({
29+
storage: storage,
30+
fileFilter: (req, file, cb) => {
31+
const fieldExtensionMap = {
32+
sdfFile: ['.sdf'],
33+
verilogFile: ['.v']
34+
};
35+
36+
// Check if the file field name is in the map
37+
const fileExtension = extname(file.originalname).toLowerCase();
38+
const allowedExtensions = fieldExtensionMap[file.fieldname];
39+
40+
if (allowedExtensions && allowedExtensions.includes(fileExtension)) {
41+
cb(null, true);
42+
} else {
43+
req.fileValidationError = `Invalid file(s) format.`;
44+
cb(null, false);
45+
}
46+
},
47+
});
48+
49+
50+
// Endpoint for uploading and parsing SDF & Verilog file
51+
app.post('/api/upload', upload.fields([{ name: 'sdfFile' }, { name: 'verilogFile' }]), async (req, res) => {
52+
try {
53+
// verify if files are uploaded
54+
if (req.fileValidationError) {
55+
return res.status(400).send(req.fileValidationError);
56+
}
57+
58+
const sdfFile = req.files?.['sdfFile']?.[0];
59+
const verilogFile = req.files?.['verilogFile']?.[0];
60+
61+
// Check if files are uploaded
62+
if (!sdfFile || !verilogFile) {
63+
return res.status(400).send('Both SDF and Verilog files must be uploaded.');
64+
}
65+
66+
const sdfContent = sdfFile.buffer.toString('utf-8').trim();
67+
const verilogContent = verilogFile.buffer.toString('utf-8').trim();
68+
69+
if (!sdfContent || !verilogContent) {
70+
return res.status(400).send('One or both uploaded files are empty.');
71+
}
72+
73+
// Parse SDF and Verilog files
74+
let sdfData, verilogData, commonInstances;
75+
try {
76+
sdfData = parseSDF(sdfContent);
77+
} catch (error) {
78+
return res.status(500).send('Error parsing SDF file.');
79+
}
80+
81+
try {
82+
verilogData = parseVerilog(verilogContent);
83+
} catch (error) {
84+
return res.status(500).send('Error parsing Verilog file.');
85+
}
86+
87+
try {
88+
commonInstances = mergeJsonForD3(verilogData, sdfData);
89+
} catch (error) {
90+
return res.status(500).send('Error merging files.');
91+
}
92+
93+
// Save parsed SDF and Verilog files
94+
try {
95+
const projectName = req.body.projectName;
96+
if (!projectName) {
97+
return res.status(400).send('Project name is required.');
98+
}
99+
100+
//try if folder 'parsed_files' exists
101+
try {
102+
await fs.access(join(__dirname, 'parsed_files'));
103+
} catch (error) {
104+
try {
105+
await fs.mkdir(join(__dirname, 'parsed_files'));
106+
} catch (error) {
107+
return res.status(500).send('Error creating directory.');
108+
}
109+
}
110+
111+
const projectJSON_Path = join(__dirname, 'parsed_files', `${projectName}.json`);
112+
113+
//check if files exists
114+
try {
115+
// check if file exists
116+
await fs.access(projectJSON_Path);
117+
return res.status(400).send('The project already exists.');
118+
119+
} catch (error) {
120+
try {
121+
await fs.writeFile(join(projectJSON_Path), JSON.stringify(commonInstances, null, 2));
122+
res.status(200).send('Files uploaded successfully.');
123+
124+
} catch (error) {
125+
res.status(500).send('Error saving parsed JSON files.');
126+
}
127+
}
128+
129+
} catch (error) {
130+
res.status(500).send('Error saving parsed JSON files.');
131+
}
132+
133+
} catch (error) {
134+
res.status(500).send('Unexpected server error.');
135+
}
136+
});
137+
138+
// Endpoint to show a error message if no project name is provided
139+
app.get('/api/map', (req, res) => {
140+
return res.status(400).send('Project name is required.');
141+
});
142+
143+
// Endpoint API for sending parsed SDF file
144+
app.get('/api/map/:projectName', async (req, res) => {
145+
try {
146+
const projectName = req.params.projectName;
147+
if (!projectName) {
148+
return res.status(400).send('Project name is required.');
149+
}
150+
151+
// Construct the file path using string concatenation
152+
const jsonFilePath = join(__dirname, 'parsed_files', `${projectName}.json`);
153+
154+
// Check if the file exists
155+
await fs.access(jsonFilePath);
156+
157+
// Read the file content
158+
const jsonData = await fs.readFile(jsonFilePath, 'utf-8');
159+
res.json(JSON.parse(jsonData));
160+
161+
} catch (error) {
162+
if (error.code === 'ENOENT') {
163+
// File does not exist
164+
return res.status(400).send('Project not found.');
165+
}
166+
res.status(500).send('Error reading parsed SDF JSON file.');
167+
}
168+
});
169+
170+
// Endpoint to delete a parsed SDF JSON file
171+
app.delete('/api/delete-project/:projectName', async (req, res) => {
172+
try {
173+
const projectName = req.params.projectName;
174+
175+
// Validate projectName
176+
if (!projectName || typeof projectName !== 'string') {
177+
return res.status(400).send('Invalid project name.');
178+
}
179+
180+
const projectPath = join(__dirname, 'parsed_files', `${projectName}.json`);
181+
182+
try {
183+
// verify if file exists
184+
await fs.access(projectPath);
185+
} catch (err) {
186+
// file does not exist
187+
return res.status(404).send('Project does not exist.');
188+
}
189+
190+
// Delete file
191+
await fs.unlink(projectPath);
192+
res.send('File deleted successfully.');
193+
194+
} catch (error) {
195+
res.status(500).send('Error deleting file, please try again later.');
196+
}
197+
});
198+
199+
200+
// Endpoint to list all SDF files
201+
app.get('/api/list', async (req, res) => {
202+
try {
203+
const directoryPath = join(__dirname, 'parsed_files');
204+
205+
// Check if the directory exists
206+
try {
207+
await fs.access(directoryPath);
208+
} catch (error) {
209+
// Create the directory if it doesn't exist
210+
await fs.mkdir(directoryPath, { recursive: true });
211+
}
212+
213+
const entries = await fs.readdir(directoryPath, { withFileTypes: true });
214+
215+
// Prepare an array to hold file information
216+
const filesInfo = [];
217+
218+
// Iterate over entries to get file names and creation dates
219+
for (const entry of entries) {
220+
if (entry.isFile() && entry.name.endsWith('.json')) {
221+
const filePath = join(directoryPath, entry.name);
222+
const stats = await fs.stat(filePath);
223+
224+
// Format the date to only include the date part (YYYY-MM-DD)
225+
const createdDate = stats.birthtime.toISOString().split('T')[0];
226+
227+
filesInfo.push({
228+
name: entry.name.replace('.json', ''),
229+
createdDate
230+
});
231+
}
232+
}
233+
234+
res.json(filesInfo);
235+
} catch (error) {
236+
res.status(500).send('Error listing files.');
237+
}
238+
});
239+
240+
export const server = app.listen(PORT, () => {});

0 commit comments

Comments
 (0)