Skip to content

Commit 3185507

Browse files
authored
Merge pull request #3208 from bramstroker/feat/manufacturer-api
Add API endpoint to retrieve manufacturer info
2 parents 9003d23 + e983356 commit 3185507

1 file changed

Lines changed: 146 additions & 118 deletions

File tree

utils/library_downloader/src/index.ts

Lines changed: 146 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import express, { Express, Request, Response, NextFunction } from "express";
1+
import express, {Express, Request, Response, NextFunction} from "express";
22
import dotenv from "dotenv";
33
import cors from "cors";
44
import apicache from "apicache";
5-
import { Octokit } from "@octokit/rest";
5+
import {Octokit} from "@octokit/rest";
66
import pino from "pino";
77
import promClient from "prom-client";
88
import promBundle from "express-prom-bundle";
@@ -21,7 +21,7 @@ const defaultBranch = process.env.REPO_BRANCH || "master"
2121
const app: Express = express();
2222
const logger = pino(
2323
{
24-
level: logLevel
24+
level: logLevel
2525
}
2626
);
2727
const collectDefaultMetrics = promClient.collectDefaultMetrics;
@@ -47,16 +47,21 @@ let promDownloadCounter = new promClient.Counter({
4747
promClient.register.registerMetric(promDownloadCounter);
4848

4949
const 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
});
5656
app.use(metricsMiddleware)
5757

5858
app.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
6166
apicache.options({
6267
statusCodes: {
@@ -83,141 +88,141 @@ const verifyToken = (req: Request, res: Response, next: NextFunction) => {
8388

8489
const 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

99104
const getRawBaseUri: (repository: Repository) => string = (repository: Repository) => {
100105
return `https://raw.githubusercontent.com/${repository.owner}/${repository.repo}/${repository.branch}/${repository.path}`
101106
}
102107

103108
app.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

195200
app.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

223228
app.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

Comments
 (0)