11import 'dotenv/config'
2- import express from 'express' ;
2+ import express , { Express } from 'express' ;
33import rateLimit from 'express-rate-limit' ;
44import bodyParser from 'body-parser' ;
55import cors from 'cors' ;
66import 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' ;
137import { 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+ } ;
0 commit comments