1- import express , { Express , Request , Response , NextFunction } from "express" ;
1+ import express , { Express , Request , Response , NextFunction } from "express" ;
22import dotenv from "dotenv" ;
33import cors from "cors" ;
44import apicache from "apicache" ;
5- import { Octokit } from "@octokit/rest" ;
5+ import { Octokit } from "@octokit/rest" ;
66import pino from "pino" ;
77import promClient from "prom-client" ;
88import promBundle from "express-prom-bundle" ;
@@ -21,7 +21,7 @@ const defaultBranch = process.env.REPO_BRANCH || "master"
2121const app : Express = express ( ) ;
2222const logger = pino (
2323 {
24- level : logLevel
24+ level : logLevel
2525 }
2626) ;
2727const collectDefaultMetrics = promClient . collectDefaultMetrics ;
@@ -47,16 +47,21 @@ let promDownloadCounter = new promClient.Counter({
4747promClient . register . registerMetric ( promDownloadCounter ) ;
4848
4949const metricsMiddleware = promBundle ( {
50- autoregister : false ,
51- includeMethod : true ,
52- includePath : true ,
53- includeStatusCode : true ,
54- includeUp : true ,
50+ autoregister : false ,
51+ includeMethod : true ,
52+ includePath : true ,
53+ includeStatusCode : true ,
54+ includeUp : true ,
5555} ) ;
5656app . use ( metricsMiddleware )
5757
5858app . use ( cors ( ) )
5959
60+ app . use ( ( req , res , next ) => {
61+ console . log ( `Request received: ${ req . method } ${ req . url } ` ) ;
62+ next ( ) ;
63+ } ) ;
64+
6065// API cache middleware
6166apicache . options ( {
6267 statusCodes : {
@@ -83,141 +88,141 @@ const verifyToken = (req: Request, res: Response, next: NextFunction) => {
8388
8489const getRepository : ( req : Request ) => Repository = ( req : Request ) => {
8590 const repositoryHeader = req . header ( "X-Powercalc-Repository" )
86- if ( repositoryHeader ) {
87- const parts = repositoryHeader . split ( "/" )
88-
89- return {
90- owner : parts [ 0 ] ,
91- repo : parts [ 1 ] ,
92- branch : parts [ 2 ] ,
93- path : parts . slice ( 3 ) . join ( '/' )
94- }
91+ if ( repositoryHeader ) {
92+ const parts = repositoryHeader . split ( "/" )
93+
94+ return {
95+ owner : parts [ 0 ] ,
96+ repo : parts [ 1 ] ,
97+ branch : parts [ 2 ] ,
98+ path : parts . slice ( 3 ) . join ( '/' )
9599 }
96- return { owner : defaultOwner , repo : defaultRepo , path : defaultPath , branch : defaultBranch }
100+ }
101+ return { owner : defaultOwner , repo : defaultRepo , path : defaultPath , branch : defaultBranch }
97102} ;
98103
99104const getRawBaseUri : ( repository : Repository ) => string = ( repository : Repository ) => {
100105 return `https://raw.githubusercontent.com/${ repository . owner } /${ repository . repo } /${ repository . branch } /${ repository . path } `
101106}
102107
103108app . get (
104- "/download/:manufacturer/:model" ,
105- cache ( "1 hour" ) ,
106- async ( req : Request , res : Response ) => {
107- const octokit = new Octokit ( {
108- auth : githubToken ,
109- } ) ;
110- const manufacturer = req . params . manufacturer ;
111- const model = req . params . model ;
112- if ( ! manufacturer || ! model ) {
113- logger . error (
114- "Manufacturer %s or model %s not provided" ,
115- manufacturer ,
116- model
117- ) ;
118- res . status ( 422 ) . json ( { message : "No manufacturer or model provided" } ) ;
119- return ;
120- }
109+ "/download/:manufacturer/:model" ,
110+ cache ( "1 hour" ) ,
111+ async ( req : Request , res : Response ) => {
112+ const octokit = new Octokit ( {
113+ auth : githubToken ,
114+ } ) ;
115+ const manufacturer = req . params . manufacturer ;
116+ const model = req . params . model ;
117+ if ( ! manufacturer || ! model ) {
118+ logger . error (
119+ "Manufacturer %s or model %s not provided" ,
120+ manufacturer ,
121+ model
122+ ) ;
123+ res . status ( 422 ) . json ( { message : "No manufacturer or model provided" } ) ;
124+ return ;
125+ }
121126
122- const repository = getRepository ( req )
123- logger . debug ( "Repository: %s/%s/%s" , repository . owner , repository . repo , repository . path )
127+ const repository = getRepository ( req )
128+ logger . debug ( "Repository: %s/%s/%s" , repository . owner , repository . repo , repository . path )
124129
125- const fetchContents = async (
126- path : string ,
127- newPath : string | null = null ,
128- ) : Promise < LibraryFile [ ] > => {
129- if ( newPath === null ) {
130- newPath = path ;
131- }
130+ const fetchContents = async (
131+ path : string ,
132+ newPath : string | null = null ,
133+ ) : Promise < LibraryFile [ ] > => {
134+ if ( newPath === null ) {
135+ newPath = path ;
136+ }
132137
133- let { data } = await octokit . repos . getContent ( {
134- owner : repository . owner ,
135- repo : repository . repo ,
136- path : newPath ,
137- ref : repository . branch
138- } ) ;
138+ let { data} = await octokit . repos . getContent ( {
139+ owner : repository . owner ,
140+ repo : repository . repo ,
141+ path : newPath ,
142+ ref : repository . branch
143+ } ) ;
139144
140- if ( ! Array . isArray ( data ) ) {
141- data = [ data ]
142- }
145+ if ( ! Array . isArray ( data ) ) {
146+ data = [ data ]
147+ }
143148
144- const subContents = await Promise . all (
145- data . map ( async ( item ) : Promise < LibraryFile [ ] > => {
146- logger . debug ( item . type )
147- if ( item . type === "file" ) {
148- if ( ! item . path . startsWith ( path ) ) {
149- throw new Error ( "No match found." ) ;
150- }
151- const modifiedPath = item . path . substring ( path . length + 1 ) ;
152- return [ { path : modifiedPath , url : item . download_url ?? "" } ] ;
153- } else if ( item . type === "symlink" ) {
154- const target = item . target
155- return await fetchContents ( target . replace ( "../" , repository . path + "/" ) )
156- } else if ( item . type === "dir" ) {
157- const newPath = `${ path } /${ item . name } ` ;
158- return await fetchContents ( path , newPath ) ;
159- } else {
160- return [ ] ;
161- }
162- } )
163- ) ;
164-
165- return subContents . flat ( ) ;
166- } ;
167-
168- const labels = { manufacturer : manufacturer , model : model } ;
169-
170- try {
171- const libraryPath = repository . path ;
172- const pattern = req . query . includePlots ? '.*' : '^(?!.*\.png$).*'
173- let files = await fetchContents (
174- libraryPath + "/" + manufacturer + "/" + model
175- ) ;
176- files = files . filter ( ( item ) => new RegExp ( pattern ) . test ( item . path ) )
177- if ( files . length === 0 ) {
178- logger . error ( "No data found for: %s/%s" , manufacturer , model ) ;
179- res . status ( 404 ) . json ( { message : "No download url's found" } ) ;
180- return ;
149+ const subContents = await Promise . all (
150+ data . map ( async ( item ) : Promise < LibraryFile [ ] > => {
151+ logger . debug ( item . type )
152+ if ( item . type === "file" ) {
153+ if ( ! item . path . startsWith ( path ) ) {
154+ throw new Error ( "No match found." ) ;
155+ }
156+ const modifiedPath = item . path . substring ( path . length + 1 ) ;
157+ return [ { path : modifiedPath , url : item . download_url ?? "" } ] ;
158+ } else if ( item . type === "symlink" ) {
159+ const target = item . target
160+ return await fetchContents ( target . replace ( "../" , repository . path + "/" ) )
161+ } else if ( item . type === "dir" ) {
162+ const newPath = `${ path } /${ item . name } ` ;
163+ return await fetchContents ( path , newPath ) ;
164+ } else {
165+ return [ ] ;
166+ }
167+ } )
168+ ) ;
169+
170+ return subContents . flat ( ) ;
171+ } ;
172+
173+ const labels = { manufacturer : manufacturer , model : model } ;
174+
175+ try {
176+ const libraryPath = repository . path ;
177+ const pattern = req . query . includePlots ? '.*' : '^(?!.*\.png$).*'
178+ let files = await fetchContents (
179+ libraryPath + "/" + manufacturer + "/" + model
180+ ) ;
181+ files = files . filter ( ( item ) => new RegExp ( pattern ) . test ( item . path ) )
182+ if ( files . length === 0 ) {
183+ logger . error ( "No data found for: %s/%s" , manufacturer , model ) ;
184+ res . status ( 404 ) . json ( { message : "No download url's found" } ) ;
185+ return ;
186+ }
187+ logger . info ( "Data successfully retrieved for %s/%s" , manufacturer , model ) ;
188+ promDownloadCounter . inc ( labels ) ;
189+ res . json ( files ) ;
190+ } catch ( error ) {
191+ logger . error ( "Error fetching data: %s" , error ) ;
192+ logger . error ( "Model not found %s/%s" , manufacturer , model ) ;
193+ res
194+ . status ( 404 )
195+ . json ( { message : "Model not found %s/%s" , manufacturer, model} ) ;
181196 }
182- logger . info ( "Data successfully retrieved for %s/%s" , manufacturer , model ) ;
183- promDownloadCounter . inc ( labels ) ;
184- res . json ( files ) ;
185- } catch ( error ) {
186- logger . error ( "Error fetching data: %s" , error ) ;
187- logger . error ( "Model not found %s/%s" , manufacturer , model ) ;
188- res
189- . status ( 404 )
190- . json ( { message : "Model not found %s/%s" , manufacturer, model } ) ;
191197 }
192- }
193198) ;
194199
195200app . get ( "/profile/:manufacturer/:model" , cache ( "1 hour" ) , async ( req : Request , res : Response ) => {
196201 const repository = getRepository ( req )
197202
198203 const manufacturer = req . params . manufacturer ;
199- const model = req . params . model ;
200- if ( ! manufacturer || ! model ) {
201- logger . error (
204+ const model = req . params . model ;
205+ if ( ! manufacturer || ! model ) {
206+ logger . error (
202207 "Manufacturer %s or model %s not provided" ,
203208 manufacturer ,
204209 model
205- ) ;
206- res . status ( 422 ) . json ( { message : "No manufacturer or model provided" } ) ;
207- return ;
208- }
210+ ) ;
211+ res . status ( 422 ) . json ( { message : "No manufacturer or model provided" } ) ;
212+ return ;
213+ }
209214
210- const url = getRawBaseUri ( repository ) + '/' + manufacturer + '/' + model + '/model.json' ;
211- logger . debug ( "Fetching profile: %s/%s" , manufacturer , model ) ;
215+ const url = getRawBaseUri ( repository ) + '/' + manufacturer + '/' + model + '/model.json' ;
216+ logger . debug ( "Fetching profile: %s/%s" , manufacturer , model ) ;
212217
213- try {
214- const resp = await fetch ( url ) ;
215- res . set ( 'Cache-Control' , 'public, max-age=3600' ) ;
216- res . json ( await resp . json ( ) ) ;
217- } catch ( error ) {
218- logger . error ( "Error fetching profile: %s" , error ) ;
219- res . status ( 404 ) . json ( { message : "Could not find profile" } ) ;
220- }
218+ try {
219+ const resp = await fetch ( url ) ;
220+ res . set ( 'Cache-Control' , 'public, max-age=3600' ) ;
221+ res . json ( await resp . json ( ) ) ;
222+ } catch ( error ) {
223+ logger . error ( "Error fetching profile: %s" , error ) ;
224+ res . status ( 404 ) . json ( { message : "Could not find profile" } ) ;
225+ }
221226} ) ;
222227
223228app . get ( "/library" , cache ( "1 hour" ) , async ( req : Request , res : Response ) => {
@@ -231,7 +236,30 @@ app.get("/library", cache("1 hour"), async (req: Request, res: Response) => {
231236 res . json ( await resp . json ( ) ) ;
232237 } catch ( error ) {
233238 logger . error ( "Error fetching library: %s" , error ) ;
234- res . status ( 500 ) . json ( { message : "Error fetching library" } ) ;
239+ res . status ( 500 ) . json ( { message : "Error fetching library" } ) ;
240+ }
241+ } ) ;
242+
243+ app . get ( "/manufacturer/:manufacturer" , cache ( "1 hour" ) , async ( req : Request , res : Response ) => {
244+ const repository = getRepository ( req )
245+
246+ const manufacturer = req . params . manufacturer ;
247+ if ( ! manufacturer ) {
248+ logger . error ( "Manufacturer not provided" ) ;
249+ res . status ( 422 ) . json ( { message : "No manufacturer provided" } ) ;
250+ return ;
251+ }
252+
253+ const url = getRawBaseUri ( repository ) + '/' + manufacturer + '/manufacturer.json' ;
254+ logger . debug ( "Fetching manufacturer: %s" , manufacturer ) ;
255+
256+ try {
257+ const resp = await fetch ( url ) ;
258+ res . set ( 'Cache-Control' , 'public, max-age=3600' ) ;
259+ res . json ( await resp . json ( ) ) ;
260+ } catch ( error ) {
261+ logger . error ( "Error fetching manufacturer: %s" , error ) ;
262+ res . status ( 404 ) . json ( { message : "Could not find manufacturer" } ) ;
235263 }
236264} ) ;
237265
0 commit comments