Skip to content

Commit 0af8acf

Browse files
committed
feat: add api versioning
1 parent 3886299 commit 0af8acf

File tree

4 files changed

+58
-35
lines changed

4 files changed

+58
-35
lines changed

src/index.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
import { Hono } from 'hono'
22
import type { AppContext } from './types/app.js';
3-
import { createStorageFromEnv, LocalStorageAdapter } from './storage/index.js'
4-
import extensionsRouter from './routes/extensions.js'
5-
import localStorageRouter from './routes/storage.js'
63
import { prisma } from './db.js';
74
import { VALID_PLATFORMS } from './constants/platforms.js';
5+
import { createStorageFromEnv, LocalStorageAdapter } from './storage/index.js';
86
import { ipMiddleware } from './middleware/ip.js';
9-
10-
const app = new Hono<AppContext>()
11-
const storage = createStorageFromEnv();
12-
const baseUrl = process.env.API_BASE_URL || 'http://localhost:3000';
7+
import storageRouter from './routes/storage.js';
8+
import v1 from './routes/v1';
139

1410
await prisma.$transaction(
1511
VALID_PLATFORMS.map(p => prisma.extensionPlatform.upsert({
@@ -19,24 +15,24 @@ await prisma.$transaction(
1915
})),
2016
);
2117

22-
// Extract client IP from reverse proxy headers
18+
const app = new Hono<AppContext>()
19+
const storage = createStorageFromEnv();
20+
2321
app.use('*', ipMiddleware());
2422

25-
// Inject shared context variables
2623
app.use('*', async (c, next) => {
2724
c.set('storage', storage)
28-
c.set('baseUrl', baseUrl)
2925
await next()
3026
})
3127

28+
if (storage instanceof LocalStorageAdapter) {
29+
app.route('/', storageRouter);
30+
}
31+
3232
app.get('/', (c) => {
3333
return c.json({ message: 'Vicinae Backend' })
3434
})
3535

36-
app.route('/', extensionsRouter)
37-
38-
if (storage instanceof LocalStorageAdapter) {
39-
app.route('/', localStorageRouter);
40-
}
36+
app.route('/v1', v1);
4137

42-
export default app
38+
export default app;

src/routes/v1/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Hono } from 'hono';
2+
import { createStorageFromEnv, LocalStorageAdapter } from '../../storage/index.js';
3+
import type { AppContext } from '../../types/app.js';
4+
import storeRouter from './store.js'
5+
import localStorageRouter from '../storage.js'
6+
7+
const storage = createStorageFromEnv();
8+
const baseUrl = process.env.API_BASE_URL || 'http://localhost:3000';
9+
10+
const v1 = new Hono<AppContext>()
11+
12+
// Inject shared context variables
13+
v1.use('*', async (c, next) => {
14+
c.set('baseUrl', `${baseUrl}/v1`)
15+
await next()
16+
17+
})
18+
19+
v1.route('/store', storeRouter)
20+
21+
if (storage instanceof LocalStorageAdapter) {
22+
v1.route('/', localStorageRouter);
23+
}
24+
25+
export default v1;
26+
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { Hono } from 'hono';
2-
import type { StorageAdapter } from '../storage/index.js';
3-
import manifestSchema from '../schemas/manifest.js';
2+
import type { StorageAdapter } from '../../storage/index.js';
3+
import manifestSchema from '../../schemas/manifest.js';
44
import { z } from 'zod';
55
import * as JSZip from 'jszip';
6-
import { prisma } from '../db.js';
7-
import { computeChecksum } from '../utils/checksum.js';
8-
import { updateTrendingStatus } from '../utils/trending.js';
9-
import { getGitHubAvatarUrl } from '../utils/avatar.js';
10-
import { fetchGitHubUser, getDisplayName } from '../utils/github.js';
11-
import { getExtensionGitHubUrls, buildAssetUrl } from '../utils/repository.js';
12-
import { parseIcon } from '../utils/icons.js';
13-
import { getMimeType } from '../utils/mime.js';
14-
import type { AppContext } from '../types/app.js';
15-
import { slugify } from '../utils/slugify.js';
6+
import { prisma } from '../../db.js';
7+
import { computeChecksum } from '../../utils/checksum.js';
8+
import { updateTrendingStatus } from '../../utils/trending.js';
9+
import { getGitHubAvatarUrl } from '../../utils/avatar.js';
10+
import { fetchGitHubUser, getDisplayName } from '../../utils/github.js';
11+
import { getExtensionGitHubUrls, buildAssetUrl } from '../../utils/repository.js';
12+
import { parseIcon } from '../../utils/icons.js';
13+
import { getMimeType } from '../../utils/mime.js';
14+
import type { AppContext } from '../../types/app.js';
15+
import { slugify } from '../../utils/slugify.js';
1616

1717
const app = new Hono<AppContext>();
1818

@@ -111,7 +111,7 @@ async function formatExtensionResponse(
111111
})
112112
);
113113

114-
const downloadUrl = `${baseUrl}/extensions/${authorHandle.toLowerCase()}/${extension.name}/download`;
114+
const downloadUrl = `${baseUrl}/store/${authorHandle.toLowerCase()}/${extension.name}/download`;
115115

116116
return {
117117
id: extension.id,
@@ -457,7 +457,7 @@ app.post('/extension/upload', async (c) => {
457457
}
458458
});
459459

460-
app.get('/extensions/categories', async (c) => {
460+
app.get('/categories', async (c) => {
461461
const categories = await prisma.extensionCategory.findMany({
462462
select: {
463463
id: true,
@@ -473,7 +473,7 @@ app.get('/extensions/categories', async (c) => {
473473
})));
474474
});
475475

476-
app.get('/extensions/search', async (c) => {
476+
app.get('/search', async (c) => {
477477
try {
478478
const query = c.req.query('q');
479479
const page = Math.max(1, parseInt(c.req.query('page') || '1', 10));
@@ -559,7 +559,7 @@ app.get('/extensions/search', async (c) => {
559559
const downloadIpMap = new Map<string, Set<string>>();
560560

561561
// Get detailed information about a specific extension
562-
app.get('/extensions/:author/:name', async (c) => {
562+
app.get('/:author/:name', async (c) => {
563563
const author = c.req.param('author');
564564
const name = c.req.param('name');
565565
const storage = c.var.storage;
@@ -578,7 +578,7 @@ app.get('/extensions/:author/:name', async (c) => {
578578
// Download zip archive and count download
579579
// We use an in-memory IP map to not duplicate downloads for the same IP per extension.
580580
// GitHub usernames are case-insensitive, so we normalize to lowercase for lookups
581-
app.get('/extensions/:author/:name/download', async (c) => {
581+
app.get('/:author/:name/download', async (c) => {
582582
const author = c.req.param('author').toLowerCase(); // Normalize to lowercase
583583
const name = c.req.param('name');
584584
const storage = c.var.storage;
@@ -619,7 +619,7 @@ app.get('/extensions/:author/:name/download', async (c) => {
619619
});
620620
});
621621

622-
app.get('/extensions/list', async (c) => {
622+
app.get('/list', async (c) => {
623623
try {
624624
const page = Math.max(1, parseInt(c.req.query('page') || '1', 10));
625625
const limit = Math.min(
@@ -693,7 +693,7 @@ app.get('/extensions/list', async (c) => {
693693
}
694694
});
695695

696-
app.post('/extensions/update-trending', async (c) => {
696+
app.post('/update-trending', async (c) => {
697697
const authHeader = c.req.header('Authorization');
698698
if (!authHeader || authHeader !== `Bearer ${API_SECRET}`) {
699699
return c.json({ error: 'Unauthorized' }, 401);

src/types/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export type AppContext = {
55
storage: StorageAdapter
66
clientIp: string
77
baseUrl: string
8+
version: `v${number}`;
89
}
910
}

0 commit comments

Comments
 (0)