feat(TRUEREF-0002): implement repository management service and REST API

Add RepositoryService with full CRUD, ID resolution helpers, input
validation, six SvelteKit API routes (GET/POST /api/v1/libs,
GET/PATCH/DELETE /api/v1/libs/:id, POST /api/v1/libs/:id/index), and
37 unit tests covering all service operations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Giancarmine Salucci
2026-03-22 17:43:06 +01:00
parent f57b622505
commit 3d1bef5003
8 changed files with 1146 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
/**
* GET /api/v1/libs — list all repositories
* POST /api/v1/libs — add a new repository
*/
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 { handleServiceError } from '$lib/server/utils/validation';
function getService() {
return new RepositoryService(getClient());
}
export const GET: RequestHandler = ({ url }) => {
try {
const service = getService();
const state = url.searchParams.get('state') as
| 'pending'
| 'indexing'
| 'indexed'
| 'error'
| undefined;
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '50'), 200);
const offset = parseInt(url.searchParams.get('offset') ?? '0');
const libraries = service.list({ state: state ?? undefined, limit, offset });
const total = service.count(state ?? undefined);
// Augment each library with its versions array
const enriched = libraries.map((repo) => ({
...repo,
versions: service.getVersions(repo.id)
}));
return json({ libraries: enriched, total, limit, offset });
} catch (err) {
return handleServiceError(err);
}
};
export const POST: RequestHandler = async ({ request }) => {
try {
const body = await request.json();
const service = getService();
const repo = service.add({
source: body.source,
sourceUrl: body.sourceUrl,
title: body.title,
description: body.description,
branch: body.branch,
githubToken: body.githubToken
});
let jobResponse: { id: string; status: string } | null = null;
if (body.autoIndex !== false) {
const job = service.createIndexingJob(repo.id);
jobResponse = { id: job.id, status: job.status };
}
return json(
{ library: repo, ...(jobResponse ? { job: jobResponse } : {}) },
{ status: 201 }
);
} catch (err) {
return handleServiceError(err);
}
};
export const OPTIONS: RequestHandler = () => {
return new Response(null, {
status: 204,
headers: corsHeaders()
});
};
function corsHeaders(): Record<string, string> {
return {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
};
}

View File

@@ -0,0 +1,68 @@
/**
* GET /api/v1/libs/:id — get a single repository
* PATCH /api/v1/libs/:id — update repository metadata
* DELETE /api/v1/libs/:id — delete a repository
*/
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 { handleServiceError } from '$lib/server/utils/validation';
function getService() {
return new RepositoryService(getClient());
}
export const GET: RequestHandler = ({ params }) => {
try {
const service = getService();
const id = decodeURIComponent(params.id);
const repo = service.get(id);
if (!repo) {
return json({ error: 'Repository not found', code: 'NOT_FOUND' }, { status: 404 });
}
const versions = service.getVersions(id);
return json({ ...repo, versions });
} catch (err) {
return handleServiceError(err);
}
};
export const PATCH: RequestHandler = async ({ params, request }) => {
try {
const service = getService();
const id = decodeURIComponent(params.id);
const body = await request.json();
const updated = service.update(id, {
title: body.title,
description: body.description,
branch: body.branch,
githubToken: body.githubToken
});
return json(updated);
} catch (err) {
return handleServiceError(err);
}
};
export const DELETE: RequestHandler = ({ params }) => {
try {
const service = getService();
const id = decodeURIComponent(params.id);
service.remove(id);
return new Response(null, { status: 204 });
} catch (err) {
return handleServiceError(err);
}
};
export const OPTIONS: RequestHandler = () => {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, PATCH, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
});
};

View File

@@ -0,0 +1,43 @@
/**
* POST /api/v1/libs/:id/index — trigger an indexing job for a repository.
*/
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 { handleServiceError, NotFoundError } from '$lib/server/utils/validation';
export const POST: RequestHandler = async ({ params, request }) => {
try {
const service = new RepositoryService(getClient());
const id = decodeURIComponent(params.id);
const repo = service.get(id);
if (!repo) throw new NotFoundError(`Repository ${id} not found`);
let versionId: string | undefined;
try {
const body = await request.json();
versionId = body.version ?? undefined;
} catch {
// body is optional
}
const job = service.createIndexingJob(id, versionId);
return json({ job }, { status: 202 });
} catch (err) {
return handleServiceError(err);
}
};
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'
}
});
};