Files
web-app-launcher/src/routes/api/apps/quick-add/+server.ts
T
alexei.dolgolyov dd6958b4d6 feat(phase3): PWA, auto-discovery, bookmarklet, multi-tab sync
- PWA: manifest, service worker (cache-first static, network-first API),
  offline page, install prompt banner
- Auto-discovery: Docker socket + Traefik API scanning, approval UI
- Quick-add bookmarklet: popup-based add page, favicon auto-detect
- Multi-tab sync: BroadcastChannel for theme + data changes
- i18n translations for all new strings (EN/RU)
2026-03-25 00:59:19 +03:00

72 lines
2.0 KiB
TypeScript

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { requireAuth } from '$lib/server/middleware/authenticate.js';
import * as appService from '$lib/server/services/appService.js';
import { success, error } from '$lib/server/utils/response.js';
import { z } from 'zod';
const quickAddSchema = z.object({
url: z
.string()
.url('Invalid URL')
.refine(
(u) => u.startsWith('http://') || u.startsWith('https://'),
'URL must use http or https protocol'
),
name: z.string().min(1, 'Name is required').max(200),
description: z.string().max(1000).optional()
});
/**
* POST /api/apps/quick-add — Quick-add an app with sensible defaults.
* Accepts { url, name, description? }, creates app with healthcheck enabled
* and attempts to auto-detect a favicon icon from the URL's domain.
*/
export const POST: RequestHandler = async (event) => {
const user = requireAuth(event);
let body: unknown;
try {
body = await event.request.json();
} catch {
return json(error('Invalid JSON body'), { status: 400 });
}
const parsed = quickAddSchema.safeParse(body);
if (!parsed.success) {
const messages = parsed.error.errors.map((e) => e.message).join(', ');
return json(error(messages), { status: 400 });
}
const { url, name, description } = parsed.data;
// Attempt to derive a favicon URL from the domain
let faviconUrl: string | undefined;
try {
const parsedUrl = new URL(url);
faviconUrl = `${parsedUrl.origin}/favicon.ico`;
} catch {
// URL parsing failed — skip icon detection
}
try {
const app = await appService.create({
name,
url,
description,
icon: faviconUrl,
iconType: faviconUrl ? 'url' : 'lucide',
healthcheckEnabled: true,
healthcheckInterval: 300,
healthcheckMethod: 'GET',
healthcheckExpectedStatus: 200,
healthcheckTimeout: 5000,
createdById: user.id
});
return json(success(app), { status: 201 });
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to create app';
return json(error(message), { status: 500 });
}
};