feat(TRUEREF-0014): implement repository version management
- VersionService with list, add, remove, getByTag, registerFromConfig - GitHub tag discovery helper for validating tags before indexing - Version ID format: /owner/repo/tag (e.g. /facebook/react/v18.3.0) - GET/POST /api/v1/libs/:id/versions - DELETE /api/v1/libs/:id/versions/:tag - POST /api/v1/libs/:id/versions/:tag/index Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
102
src/routes/api/v1/libs/[id]/versions/+server.ts
Normal file
102
src/routes/api/v1/libs/[id]/versions/+server.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* GET /api/v1/libs/:id/versions — list all indexed versions for a repository
|
||||
* POST /api/v1/libs/:id/versions — add a new version (tag or branch)
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client';
|
||||
import { RepositoryService } from '$lib/server/services/repository.service';
|
||||
import { VersionService } from '$lib/server/services/version.service';
|
||||
import { handleServiceError, NotFoundError, InvalidInputError } from '$lib/server/utils/validation';
|
||||
|
||||
function getServices() {
|
||||
const db = getClient();
|
||||
return {
|
||||
repoService: new RepositoryService(db),
|
||||
versionService: new VersionService(db)
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /api/v1/libs/:id/versions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const GET: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const { repoService, versionService } = getServices();
|
||||
const repositoryId = decodeURIComponent(params.id);
|
||||
|
||||
const repo = repoService.get(repositoryId);
|
||||
if (!repo) {
|
||||
throw new NotFoundError(`Repository ${repositoryId} not found`);
|
||||
}
|
||||
|
||||
const versions = versionService.list(repositoryId);
|
||||
return json({ versions });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /api/v1/libs/:id/versions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const POST: RequestHandler = async ({ params, request }) => {
|
||||
try {
|
||||
const { repoService, versionService } = getServices();
|
||||
const repositoryId = decodeURIComponent(params.id);
|
||||
|
||||
const repo = repoService.get(repositoryId);
|
||||
if (!repo) {
|
||||
throw new NotFoundError(`Repository ${repositoryId} not found`);
|
||||
}
|
||||
|
||||
let body: { tag?: unknown; title?: unknown; autoIndex?: unknown };
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
throw new InvalidInputError('Request body must be valid JSON', [
|
||||
{ field: 'body', message: 'Invalid JSON' }
|
||||
]);
|
||||
}
|
||||
|
||||
const tag = body.tag;
|
||||
if (!tag || typeof tag !== 'string' || !tag.trim()) {
|
||||
throw new InvalidInputError('tag is required', [
|
||||
{ field: 'tag', message: 'tag must be a non-empty string' }
|
||||
]);
|
||||
}
|
||||
|
||||
const title = typeof body.title === 'string' ? body.title : undefined;
|
||||
const autoIndex = body.autoIndex === true;
|
||||
|
||||
const version = versionService.add(repositoryId, tag.trim(), title);
|
||||
|
||||
let job: { id: string; status: string } | undefined;
|
||||
if (autoIndex) {
|
||||
const indexingJob = repoService.createIndexingJob(repositoryId, version.id);
|
||||
job = { id: indexingJob.id, status: indexingJob.status };
|
||||
}
|
||||
|
||||
return json({ version, ...(job ? { job } : {}) }, { status: 201 });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OPTIONS preflight
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
54
src/routes/api/v1/libs/[id]/versions/[tag]/+server.ts
Normal file
54
src/routes/api/v1/libs/[id]/versions/[tag]/+server.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* DELETE /api/v1/libs/:id/versions/:tag — remove a version and all its snippets
|
||||
*/
|
||||
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client';
|
||||
import { RepositoryService } from '$lib/server/services/repository.service';
|
||||
import { VersionService } from '$lib/server/services/version.service';
|
||||
import { handleServiceError, NotFoundError } from '$lib/server/utils/validation';
|
||||
|
||||
function getServices() {
|
||||
const db = getClient();
|
||||
return {
|
||||
repoService: new RepositoryService(db),
|
||||
versionService: new VersionService(db)
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DELETE /api/v1/libs/:id/versions/:tag
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const DELETE: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const { repoService, versionService } = getServices();
|
||||
const repositoryId = decodeURIComponent(params.id);
|
||||
const tag = decodeURIComponent(params.tag);
|
||||
|
||||
const repo = repoService.get(repositoryId);
|
||||
if (!repo) {
|
||||
throw new NotFoundError(`Repository ${repositoryId} not found`);
|
||||
}
|
||||
|
||||
versionService.remove(repositoryId, tag);
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OPTIONS preflight
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
60
src/routes/api/v1/libs/[id]/versions/[tag]/index/+server.ts
Normal file
60
src/routes/api/v1/libs/[id]/versions/[tag]/index/+server.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* POST /api/v1/libs/:id/versions/:tag/index — queue an indexing job for a specific version
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client';
|
||||
import { RepositoryService } from '$lib/server/services/repository.service';
|
||||
import { VersionService } from '$lib/server/services/version.service';
|
||||
import { handleServiceError, NotFoundError } from '$lib/server/utils/validation';
|
||||
|
||||
function getServices() {
|
||||
const db = getClient();
|
||||
return {
|
||||
repoService: new RepositoryService(db),
|
||||
versionService: new VersionService(db)
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /api/v1/libs/:id/versions/:tag/index
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const POST: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const { repoService, versionService } = getServices();
|
||||
const repositoryId = decodeURIComponent(params.id);
|
||||
const tag = decodeURIComponent(params.tag);
|
||||
|
||||
const repo = repoService.get(repositoryId);
|
||||
if (!repo) {
|
||||
throw new NotFoundError(`Repository ${repositoryId} not found`);
|
||||
}
|
||||
|
||||
const version = versionService.getByTag(repositoryId, tag);
|
||||
if (!version) {
|
||||
throw new NotFoundError(`Version ${tag} not found for repository ${repositoryId}`);
|
||||
}
|
||||
|
||||
const job = repoService.createIndexingJob(repositoryId, version.id);
|
||||
return json({ job }, { status: 202 });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OPTIONS preflight
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user