OpenAPI spec at /api/docs with a Scalar viewer, opt-in via FEAT_API_DOCS

This commit is contained in:
jarek committed 2026-08-17 09:50:34 +02:00
1 parent ba8670af66
commit da26f7f764
251 files changed
+32497 -66

No files matched your search

@@ -8,6 +8,14 @@ import {
import { authorize } from '$lib/server/authorize';
import { auditGitRepository } from '$lib/server/audit';
/**
* @openapi
* summary: List all git repositories (repositories are global, not scoped to an environment)
* resp-200: array<{id:integer!, name:string!, url:string!, branch:string!, credentialId:integer}>
* resp-200-example: [{"id":1,"name":"homelab","url":"https://github.com/example/homelab.git","branch":"main","credentialId":2}]
* resp-403: Caller lacks the git:view permission
* resp-500: Failed to read git repositories
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !await auth.can('git', 'view')) {
@@ -25,6 +33,17 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
}
};
/**
* @openapi
* summary: Create a git repository (branch defaults to main); deployment config lives on git stacks, not here
* description: credentialId from GET /api/git/credentials.
* body: {name:string!, url:string!, branch:string, credentialId:integer}
* body-example: {"name":"homelab","url":"https://github.com/example/homelab.git","branch":"main","credentialId":2}
* resp-200: {id:integer!, name:string!, url:string!, branch:string!, credentialId:integer}
* resp-400: Missing name/url, an invalid credentialId, or a duplicate repository name
* resp-403: Caller lacks the git:create permission
* resp-500: Failed to create the git repository
*/
export const POST: RequestHandler = async (event) => {
const { request, cookies } = event;
const auth = await authorize(cookies);
@@ -12,6 +12,16 @@ import { authorize } from '$lib/server/authorize';
import { auditGitRepository } from '$lib/server/audit';
import { computeAuditDiff } from '$lib/utils/diff';
/**
* @openapi
* summary: Get a single git repository by ID
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {id:integer!, name:string!, url:string!, branch:string!, credentialId:integer}
* resp-400: The id path segment is not a valid integer
* resp-403: Caller lacks the git:view permission
* resp-404: No repository exists with that ID
* resp-500: Failed to read the git repository
*/
export const GET: RequestHandler = async ({ params, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !await auth.can('git', 'view')) {
@@ -36,6 +46,19 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
}
};
/**
* @openapi
* summary: Update a git repository's basic fields (name/url/branch/credential)
* description: credentialId from GET /api/git/credentials.
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* body: {name:string, url:string, branch:string, credentialId:integer}
* body-example: {"name":"homelab","url":"https://github.com/example/homelab.git","branch":"production","credentialId":2}
* resp-200: {id:integer!, name:string!, url:string!, branch:string!, credentialId:integer}
* resp-400: Invalid id, an invalid credentialId, or a duplicate repository name
* resp-403: Caller lacks the git:edit permission
* resp-404: No repository exists with that ID
* resp-500: The update failed or the repository could not be persisted
*/
export const PUT: RequestHandler = async (event) => {
const { params, request, cookies } = event;
const auth = await authorize(cookies);
@@ -94,6 +117,17 @@ export const PUT: RequestHandler = async (event) => {
}
};
/**
* @openapi
* summary: Delete a git repository, first removing the clone directories of every git stack it backs
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {success:boolean!}
* resp-200-example: {"success":true}
* resp-400: The id path segment is not a valid integer
* resp-403: Caller lacks the git:delete permission
* resp-404: No repository exists with that ID
* resp-500: The deletion failed
*/
export const DELETE: RequestHandler = async (event) => {
const { params, cookies } = event;
const auth = await authorize(cookies);
@@ -3,6 +3,16 @@ import type { RequestHandler } from './$types';
import { getGitRepository } from '$lib/server/db';
import { deployFromRepository } from '$lib/server/git';
/**
* @openapi
* summary: Deploy the compose stack(s) defined in a git repository (clones/pulls, then runs docker compose)
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {success:boolean!, error:string}
* resp-200-example: {"success":true}
* resp-400: The id path segment is not a valid integer
* resp-404: No repository exists with that ID
* resp-500: The deployment failed
*/
export const POST: RequestHandler = async ({ params }) => {
try {
const id = parseInt(params.id);
@@ -3,6 +3,16 @@ import type { RequestHandler } from './$types';
import { getGitRepository } from '$lib/server/db';
import { syncRepository, checkForUpdates } from '$lib/server/git';
/**
* @openapi
* summary: Sync (git pull) the local clone of a repository to the latest commit on its tracked branch
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {success:boolean!, error:string}
* resp-200-example: {"success":true}
* resp-400: The id path segment is not a valid integer
* resp-404: No repository exists with that ID
* resp-500: The sync failed
*/
export const POST: RequestHandler = async ({ params }) => {
try {
const id = parseInt(params.id);
@@ -23,6 +33,16 @@ export const POST: RequestHandler = async ({ params }) => {
}
};
/**
* @openapi
* summary: Check whether the tracked branch has new commits upstream without pulling them
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {hasUpdates:boolean!, error:string}
* resp-200-example: {"hasUpdates":false}
* resp-400: The id path segment is not a valid integer
* resp-404: No repository exists with that ID
* resp-500: The update check failed
*/
export const GET: RequestHandler = async ({ params }) => {
// Check for updates without syncing
try {
@@ -3,6 +3,16 @@ import type { RequestHandler } from './$types';
import { getGitRepository } from '$lib/server/db';
import { testRepository } from '$lib/server/git';
/**
* @openapi
* summary: Test connectivity/authentication to a saved repository using its stored credential
* path: id:integer! Git repository ID (from GET /api/git/repositories)
* resp-200: {success:boolean!, error:string}
* resp-200-example: {"success":true}
* resp-400: The id path segment is not a valid integer
* resp-404: No repository exists with that ID
* resp-500: The connectivity test failed
*/
export const POST: RequestHandler = async ({ params }) => {
try {
const id = parseInt(params.id);
@@ -14,6 +14,18 @@ import { authorize } from '$lib/server/authorize';
* credentialId?: number; // Optional credential ID from database
* }
*/
/**
* @openapi
* summary: Test an unsaved repository configuration (url/branch/credentialId) before creating it
* description: credentialId from GET /api/git/credentials.
* body: {url:string!, branch:string, credentialId:integer}
* body-example: {"url":"https://github.com/example/homelab.git","branch":"main","credentialId":2}
* resp-200: {success:boolean!, error:string}
* resp-200-example: {"success":true}
* resp-400: The url field is missing
* resp-403: Caller lacks the settings:manage permission
* resp-500: The connectivity test failed
*/
export const POST: RequestHandler = async ({ request, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !await auth.can('settings', 'manage')) {