Skip to content

Commit 183b8e8

Browse files
authored
feat: Add backend (#42)
1 parent 169c171 commit 183b8e8

26 files changed

Lines changed: 531 additions & 49 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
tsconfig.tsbuildinfo

backend/.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# dependencies (bun install)
2+
node_modules
3+
4+
# output
5+
out
6+
dist
7+
*.tgz
8+
9+
# code coverage
10+
coverage
11+
*.lcov
12+
13+
# logs
14+
logs
15+
_.log
16+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17+
18+
# dotenv environment variable files
19+
.env
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
.env.local
24+
25+
# caches
26+
.eslintcache
27+
.cache
28+
*.tsbuildinfo
29+
30+
# IntelliJ based IDEs
31+
.idea
32+
33+
# Finder (MacOS) folder config
34+
.DS_Store

backend/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "backend",
3+
"module": "src/index.ts",
4+
"type": "module",
5+
"private": true,
6+
"scripts": {
7+
"dev": "bun --hot run src/index.ts",
8+
"build": "tsc --build",
9+
"opt:proxy": "HTTP_PROXY=http://127.0.0.1:8080 HTTPS_PROXY=http://127.0.0.1:8080"
10+
},
11+
"dependencies": {
12+
"@types/express": "^5.0.3",
13+
"express": "^5.1.0",
14+
"mclib": "workspace:*"
15+
},
16+
"peerDependencies": {
17+
"typescript": "^5"
18+
}
19+
}

backend/src/index.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import express from 'express';
2+
import { type IRepository, ModQueryService } from 'mclib';
3+
import { CurseForgeRepository, ModrinthRepository } from 'mclib';
4+
import type { Request, Response, NextFunction } from 'express';
5+
6+
const app = express();
7+
const port = 3000;
8+
9+
function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
10+
const { method, url } = req;
11+
const start = Date.now();
12+
let responseBody: any;
13+
// Monkey-patch res.json to capture body
14+
const originalJson = res.json;
15+
res.json = function (body: any) {
16+
responseBody = body;
17+
return originalJson.call(this, body);
18+
};
19+
res.on('finish', () => {
20+
const status = res.statusCode;
21+
const duration = Date.now() - start;
22+
let logMsg = `${status} ${method} ${url} (${duration}ms)`;
23+
if (status === 500 && responseBody && responseBody.error) {
24+
logMsg += ` | error: ${responseBody.error}`;
25+
}
26+
console.log(logMsg);
27+
});
28+
next();
29+
}
30+
31+
// Middleware to parse JSON bodies
32+
app.use(express.json());
33+
app.use(loggerMiddleware);
34+
app.get('/', (_, res) => {
35+
res.send('Hello, world!');
36+
});
37+
38+
const repositories: IRepository[] = [
39+
new CurseForgeRepository(fetch),
40+
new ModrinthRepository(fetch)
41+
];
42+
const modQueryService = new ModQueryService(repositories);
43+
44+
app.post('/getMinecraftVersions', async (req: Request, res: Response) => {
45+
try {
46+
const versions = await modQueryService.getMinecraftVersions();
47+
res.json(versions);
48+
} catch (err) {
49+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
50+
}
51+
});
52+
53+
app.post('/searchMods', async (req: Request, res: Response) => {
54+
try {
55+
const { query, specifiedRepos = [], maxResults = 10 } = req.body;
56+
const results = await modQueryService.searchMods(query, specifiedRepos, maxResults);
57+
res.json(results);
58+
} catch (err) {
59+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
60+
}
61+
});
62+
63+
app.post('/getModReleasesFromMetadata', async (req: Request, res: Response) => {
64+
try {
65+
const { modMeta } = req.body;
66+
const releases = await modQueryService.getModReleasesFromMetadata(modMeta);
67+
68+
// Transform sets to arrays for JSON serialization
69+
let jsonReleases= [];
70+
for (let release of releases) {
71+
let jsonRelease = {...release, mcVersions: Array.from(release.mcVersions), loaders: Array.from(release.loaders)};
72+
jsonReleases.push(jsonRelease);
73+
}
74+
75+
res.json(jsonReleases);
76+
} catch (err) {
77+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
78+
}
79+
});
80+
81+
type GetModByDataHashRequest = {
82+
hash: string,
83+
repository: string
84+
};
85+
app.post('/getModByDataHash', async (req: Request, res: Response) => {
86+
try {
87+
const body: GetModByDataHashRequest = req.body;
88+
for (const repo of repositories) {
89+
if (repo.getRepositoryName() === body.repository) {
90+
const result = await repo.getByDataHash(body.hash);
91+
return res.json(result);
92+
}
93+
}
94+
res.json(null);
95+
} catch (err) {
96+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
97+
}
98+
});
99+
100+
app.post('/getModById', async (req: Request, res: Response) => {
101+
try {
102+
const { modId, specifiedRepos = [] } = req.body;
103+
const results = await modQueryService.searchMods(modId, specifiedRepos, 1);
104+
res.json(results[0] || null);
105+
} catch (err) {
106+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
107+
}
108+
});
109+
110+
app.listen(port, () => {
111+
console.log(`Server is running at http://localhost:${port}`);
112+
});

backend/tsconfig.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"declaration": true,
5+
"outDir": "dist",
6+
"module": "ESNext",
7+
"target": "ES6",
8+
"moduleResolution": "bundler",
9+
"strict": true,
10+
"lib": [
11+
"esnext"
12+
]
13+
},
14+
"include": [
15+
"src"
16+
],
17+
"references": [
18+
{
19+
"path": "../mclib"
20+
}
21+
]
22+
}

0 commit comments

Comments
 (0)