Skip to content

Commit 9fbfe6b

Browse files
committed
Merge branch 'main' into Nomenclature-for-Developer-Estimates-plus-Form-behavior
2 parents 71497fd + 2726233 commit 9fbfe6b

File tree

102 files changed

+3576
-2730
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+3576
-2730
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"request": "launch",
1515
"name": "Debug Backend",
1616
"type": "node",
17-
"program": "${workspaceFolder}/backend/src/app.ts",
17+
"program": "${workspaceFolder}/backend/src/index.ts",
1818
"preLaunchTask": "npm: build - backend",
1919
"cwd": "${workspaceFolder}/backend",
2020
"envFile": "${workspaceFolder}/backend/.env",

.vscode/tasks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"isBackground": true,
116116
"problemMatcher": [],
117117
"label": "npm: dev - backend with db",
118-
"detail": "tsx src/app.ts | bunyan -o short",
118+
"detail": "tsx src/index.ts | bunyan -o short",
119119
"icon": {
120120
"id": "server"
121121
},
@@ -132,7 +132,7 @@
132132
"script": "start",
133133
"path": "backend",
134134
"label": "npm: start - backend",
135-
"detail": "node dist/app.js | bunyan -o short",
135+
"detail": "node dist/index.js | bunyan -o short",
136136
"icon": {
137137
"id": "server"
138138
},

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ WORKDIR /app/backend
2929

3030
EXPOSE 80
3131

32-
CMD node dist/app.js | ./node_modules/.bin/bunyan
32+
CMD node dist/index.js | ./node_modules/.bin/bunyan

backend/__tests__/survey.test.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

backend/package-lock.json

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"version": "1.0.0",
44
"description": "Demonstrate the value of GitHub",
55
"type": "module",
6-
"main": "src/app.ts",
6+
"main": "src/index.ts",
77
"scripts": {
8-
"start": "node --enable-source-maps dist/app.js",
8+
"start": "node --enable-source-maps dist/index.js | bunyan -o short -l info",
99
"test": "jest",
1010
"build": "tsc",
11-
"dev": "tsx watch src/app.ts | bunyan -o short -l debug",
11+
"dev": "tsx watch src/index.ts | bunyan -o short -l debug",
1212
"lint": "eslint src/**/*.ts",
1313
"db:start": "docker-compose -f ../compose.yml up -d db",
1414
"dotenv": "cp -n .env.example .env || true"
@@ -25,7 +25,8 @@
2525
"octokit": "^4.0.2",
2626
"sequelize": "^6.37.5",
2727
"smee-client": "^2.0.4",
28-
"update-dotenv": "^1.1.1"
28+
"update-dotenv": "^1.1.1",
29+
"why-is-node-running": "^3.2.1"
2930
},
3031
"devDependencies": {
3132
"@eslint/js": "^9.14.0",

backend/src/app.ts

Lines changed: 152 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,162 @@
11
import 'dotenv/config'
2-
import express from 'express';
2+
import express, { Express } from 'express';
33
import rateLimit from 'express-rate-limit';
44
import bodyParser from 'body-parser';
55
import cors from 'cors';
66
import path, { dirname } from 'path';
7-
import apiRoutes from "./routes/index.js"
8-
import { dbConnect } from './database.js';
9-
import setup from './services/setup.js';
10-
import settingsService from './services/settings.service.js';
11-
import SmeeService from './services/smee.js';
12-
import logger, { expressLoggerMiddleware } from './services/logger.js';
137
import { fileURLToPath } from 'url';
8+
import * as http from 'http';
9+
import Database from './database.js';
10+
import logger, { expressLoggerMiddleware } from './services/logger.js';
11+
import GitHub from './github.js';
12+
import SettingsService from './services/settings.service.js';
13+
import apiRoutes from "./routes/index.js"
14+
import WebhookService from './services/smee.js';
15+
16+
class App {
17+
e: Express;
18+
eListener?: http.Server;
19+
baseUrl?: string;
20+
public database: Database;
21+
public github: GitHub;
22+
public settingsService: SettingsService;
1423

15-
const PORT = Number(process.env.PORT) || 80;
16-
17-
export const app = express();
18-
app.use(cors());
19-
app.use(expressLoggerMiddleware);
20-
21-
(async () => {
22-
await dbConnect();
23-
logger.info('DB Connected ✅');
24-
await settingsService.initializeSettings();
25-
logger.info('Settings loaded ✅');
26-
await SmeeService.createSmeeWebhookProxy(PORT);
27-
logger.info('Created Smee webhook proxy ✅');
28-
29-
try {
30-
await setup.createAppFromEnv();
31-
logger.info('Created GitHub App from environment ✅');
32-
} catch (error) {
33-
logger.info('Failed to create app from environment. This is expected if the app is not yet installed.', error);
24+
constructor(
25+
public port: number
26+
) {
27+
this.baseUrl = process.env.BASE_URL || 'http://localhost:' + port;
28+
this.e = express();
29+
this.port = port;
30+
this.database = new Database(process.env.JAWSDB_URL ? process.env.JAWSDB_URL : {
31+
host: process.env.MYSQL_HOST,
32+
port: Number(process.env.MYSQL_PORT) || 3306,
33+
username: process.env.MYSQL_USER,
34+
password: process.env.MYSQL_PASSWORD,
35+
database: process.env.MYSQL_DATABASE || 'value'
36+
});
37+
const webhookService = new WebhookService({
38+
url: process.env.WEBHOOK_PROXY_URL,
39+
path: '/api/github/webhooks',
40+
port
41+
});
42+
this.github = new GitHub(
43+
{
44+
appId: process.env.GITHUB_APP_ID,
45+
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
46+
webhooks: {
47+
secret: process.env.GITHUB_WEBHOOK_SECRET
48+
}
49+
},
50+
this.e,
51+
webhookService,
52+
this.baseUrl
53+
)
54+
this.settingsService = new SettingsService({
55+
baseUrl: this.baseUrl,
56+
webhookProxyUrl: process.env.GITHUB_WEBHOOK_PROXY_URL,
57+
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
58+
metricsCronExpression: '0 0 * * *',
59+
devCostPerYear: '100000',
60+
developerCount: '100',
61+
hoursPerYear: '2080',
62+
percentTimeSaved: '20',
63+
percentCoding: '20'
64+
})
3465
}
3566

36-
app.use((req, res, next) => {
37-
if (req.path === '/api/github/webhooks') {
38-
return next();
39-
}
40-
bodyParser.json()(req, res, next);
41-
}, bodyParser.urlencoded({ extended: true }));
42-
app.use('/api', apiRoutes);
43-
44-
const __filename = fileURLToPath(import.meta.url);
45-
const __dirname = dirname(__filename);
46-
const frontendPath = path.resolve(__dirname, '../../frontend/dist/github-value/browser');
47-
48-
app.use(express.static(frontendPath));
49-
app.get('*', rateLimit({
50-
windowMs: 15 * 60 * 1000, max: 5000,
51-
}), (_, res) => res.sendFile(path.join(frontendPath, 'index.html')));
52-
53-
app.listen(PORT, () => {
54-
logger.info(`Server is running at http://localhost:${PORT} 🚀`);
55-
if (process.env.WEB_URL) {
56-
logger.debug(`Frontend is running at ${process.env.WEB_URL} 🚀`);
67+
public async start() {
68+
try {
69+
logger.info('Starting application');
70+
71+
logger.info('Express setup...');
72+
this.setupExpress();
73+
logger.info('Express setup complete');
74+
75+
logger.info('Database connecting...');
76+
await this.database.connect();
77+
logger.info('Database connected');
78+
79+
logger.info('Initializing settings...');
80+
await this.initializeSettings();
81+
logger.info('Settings initialized');
82+
83+
logger.info('GitHub App starting...');
84+
await this.github.connect();
85+
logger.info('GitHub App connected');
86+
87+
return this.e;
88+
} catch (error) {
89+
await this.github.smee.connect();
90+
logger.error('Failed to start application ❌');
91+
if (error instanceof Error) {
92+
logger.error(error.message);
93+
}
94+
logger.debug(error);
5795
}
58-
});
59-
})();
96+
}
97+
98+
public async stop() {
99+
await new Promise(resolve => this.eListener?.close(resolve));
100+
await this.database.disconnect();
101+
await this.github.disconnect();
102+
}
103+
104+
private setupExpress() {
105+
this.e.use(cors());
106+
this.e.use(expressLoggerMiddleware);
107+
this.e.use((req, res, next) => {
108+
if (req.path === '/api/github/webhooks') {
109+
return next();
110+
}
111+
bodyParser.json()(req, res, next);
112+
}, bodyParser.urlencoded({ extended: true }));
113+
114+
this.e.use('/api', apiRoutes);
115+
116+
const __filename = fileURLToPath(import.meta.url);
117+
const __dirname = dirname(__filename);
118+
const frontendPath = path.resolve(__dirname, '../../frontend/dist/github-value/browser');
119+
this.e.use(express.static(frontendPath));
120+
this.e.get(
121+
'*',
122+
rateLimit({
123+
windowMs: 15 * 60 * 1000, max: 5000,
124+
}),
125+
(_, res) => res.sendFile(path.join(frontendPath, 'index.html'))
126+
);
127+
128+
this.eListener = this.e.listen(this.port);
129+
}
130+
131+
private initializeSettings() {
132+
this.settingsService.initialize()
133+
.then(async (settings) => {
134+
if (settings.webhookProxyUrl) {
135+
this.github.smee.options.url = settings.webhookProxyUrl
136+
}
137+
if (settings.webhookSecret) {
138+
this.github.setInput({
139+
webhooks: {
140+
secret: settings.webhookSecret
141+
}
142+
});
143+
}
144+
if (settings.metricsCronExpression) {
145+
this.github.cronExpression = settings.metricsCronExpression;
146+
}
147+
if (settings.baseUrl) {
148+
this.baseUrl = settings.baseUrl;
149+
}
150+
})
151+
.finally(async () => {
152+
await this.github.smee.connect()
153+
await this.settingsService.updateSetting('webhookSecret', this.github.input.webhooks?.secret || '');
154+
await this.settingsService.updateSetting('webhookProxyUrl', this.github.smee.options.url!);
155+
await this.settingsService.updateSetting('metricsCronExpression', this.github.cronExpression!);
156+
});
157+
}
158+
}
159+
160+
export {
161+
App as default
162+
};

backend/src/controllers/metrics.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import MetricsService from '../services/metrics.service.js';
44
class MetricsController {
55
async getMetrics(req: Request, res: Response): Promise<void> {
66
try {
7-
const metrics = await MetricsService.queryMetrics(req.query)
7+
const metrics = await MetricsService.getMetrics(req.query)
88
res.status(200).json(metrics);
99
} catch (error) {
1010
res.status(500).json(error);
@@ -13,7 +13,7 @@ class MetricsController {
1313

1414
async getMetricsTotals(req: Request, res: Response): Promise<void> {
1515
try {
16-
const metrics = await MetricsService.queryMetricsTotals(req.query)
16+
const metrics = await MetricsService.getMetricsTotals(req.query)
1717
res.status(200).json(metrics);
1818
} catch (error) {
1919
res.status(500).json(error);

0 commit comments

Comments
 (0)