mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
chore: remove internal docs from repo, update public documentation
Remove docs/superpowers/, .claude/ config, and PRD.md from version control (kept locally via .gitignore). Update README, CHANGELOG, VitePress docs, and .env.example to reflect recent features: Files page, teams, admin settings, persistent storage, and various API improvements.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
import Database, { type Database as DatabaseType } from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import { env } from "../config.js";
|
||||
import * as schema from "./schema.js";
|
||||
@@ -8,7 +8,7 @@ import * as schema from "./schema.js";
|
||||
// Ensure data directory exists
|
||||
mkdirSync(dirname(env.DB_PATH), { recursive: true });
|
||||
|
||||
const sqlite = new Database(env.DB_PATH);
|
||||
const sqlite: DatabaseType = new Database(env.DB_PATH);
|
||||
|
||||
// Critical SQLite pragmas for reliability
|
||||
sqlite.pragma("journal_mode = WAL");
|
||||
|
||||
@@ -18,7 +18,7 @@ export function getMaxAgeMs(): number {
|
||||
.get();
|
||||
if (row) {
|
||||
const hours = parseFloat(row.value);
|
||||
if (!isNaN(hours) && hours > 0) return hours * 60 * 60 * 1000;
|
||||
if (!Number.isNaN(hours) && hours > 0) return hours * 60 * 60 * 1000;
|
||||
}
|
||||
} catch {
|
||||
/* DB not ready yet, use env */
|
||||
|
||||
@@ -128,7 +128,7 @@ export async function ensureDefaultAdmin(): Promise<void> {
|
||||
|
||||
// ── Login attempt limit ──────────────────────────────────────────
|
||||
|
||||
const DEFAULT_LOGIN_ATTEMPT_LIMIT = 5;
|
||||
const DEFAULT_LOGIN_ATTEMPT_LIMIT = 10;
|
||||
|
||||
function getLoginAttemptLimit(): number {
|
||||
const row = db
|
||||
@@ -378,7 +378,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
||||
const teamExists = db
|
||||
.select()
|
||||
.from(schema.teams)
|
||||
.where(eq(schema.teams.id, (body as { team?: string }).team!))
|
||||
.where(eq(schema.teams.id, (body as { team?: string }).team ?? ""))
|
||||
.get();
|
||||
if (!teamExists)
|
||||
return reply.status(400).send({ error: "Team not found", code: "VALIDATION_ERROR" });
|
||||
|
||||
@@ -150,7 +150,12 @@ export async function registerPipelineRoutes(app: FastifyInstance): Promise<void
|
||||
try {
|
||||
for (let i = 0; i < pipeline.steps.length; i++) {
|
||||
const step = pipeline.steps[i];
|
||||
const toolConfig = getToolConfig(step.toolId)!;
|
||||
const toolConfig = getToolConfig(step.toolId);
|
||||
if (!toolConfig) {
|
||||
return reply
|
||||
.status(400)
|
||||
.send({ error: `Step ${i + 1}: Tool "${step.toolId}" not found` });
|
||||
}
|
||||
|
||||
// Parse settings through the schema to apply defaults
|
||||
const settings = toolConfig.settingsSchema.parse(step.settings);
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function teamsRoutes(app: FastifyInstance): Promise<void> {
|
||||
return reply.status(400).send({ error: nameError, code: "VALIDATION_ERROR" });
|
||||
}
|
||||
|
||||
const trimmedName = (body!.name as string).trim();
|
||||
const trimmedName = (body?.name ?? "").trim();
|
||||
|
||||
// Check for duplicate name (case-insensitive)
|
||||
const existing = db
|
||||
@@ -97,7 +97,7 @@ export async function teamsRoutes(app: FastifyInstance): Promise<void> {
|
||||
return reply.status(400).send({ error: nameError, code: "VALIDATION_ERROR" });
|
||||
}
|
||||
|
||||
const trimmedName = (body!.name as string).trim();
|
||||
const trimmedName = (body?.name ?? "").trim();
|
||||
|
||||
// Check for duplicate name (case-insensitive), excluding current team
|
||||
const duplicate = db
|
||||
|
||||
@@ -24,18 +24,27 @@ export interface ToolRouteConfig<T> {
|
||||
) => Promise<{ buffer: Buffer; filename: string; contentType: string }>;
|
||||
}
|
||||
|
||||
/** Type-erased config stored in the registry (settings type is widened to avoid variance issues). */
|
||||
export interface AnyToolRouteConfig {
|
||||
toolId: string;
|
||||
settingsSchema: z.ZodType<unknown, z.ZodTypeDef, unknown>;
|
||||
process: (
|
||||
inputBuffer: Buffer,
|
||||
settings: unknown,
|
||||
filename: string,
|
||||
) => Promise<{ buffer: Buffer; filename: string; contentType: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory registry of all tool configs, keyed by toolId.
|
||||
* Populated by createToolRoute() calls; used by batch processing.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const toolRegistry = new Map<string, ToolRouteConfig<any>>();
|
||||
const toolRegistry = new Map<string, AnyToolRouteConfig>();
|
||||
|
||||
/**
|
||||
* Retrieve a registered tool config by its ID.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function getToolConfig(toolId: string): ToolRouteConfig<any> | undefined {
|
||||
export function getToolConfig(toolId: string): AnyToolRouteConfig | undefined {
|
||||
return toolRegistry.get(toolId);
|
||||
}
|
||||
|
||||
@@ -55,8 +64,8 @@ export function getToolConfig(toolId: string): ToolRouteConfig<any> | undefined
|
||||
* - Response formatting
|
||||
*/
|
||||
export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig<T>): void {
|
||||
// Register in the tool registry for batch processing
|
||||
toolRegistry.set(config.toolId, config);
|
||||
// Register in the tool registry for batch processing (cast to type-erased form)
|
||||
toolRegistry.set(config.toolId, config as AnyToolRouteConfig);
|
||||
|
||||
app.post(
|
||||
`/api/v1/tools/${config.toolId}`,
|
||||
|
||||
@@ -60,10 +60,11 @@ export function registerBlurFaces(app: FastifyInstance) {
|
||||
await writeFile(inputPath, fileBuffer);
|
||||
|
||||
// Process
|
||||
const onProgress = clientJobId
|
||||
const jobIdForProgress = clientJobId;
|
||||
const onProgress = jobIdForProgress
|
||||
? (percent: number, stage: string) => {
|
||||
updateSingleFileProgress({
|
||||
jobId: clientJobId!,
|
||||
jobId: jobIdForProgress,
|
||||
phase: "processing",
|
||||
stage,
|
||||
percent,
|
||||
|
||||
@@ -71,10 +71,11 @@ export function registerEraseObject(app: FastifyInstance) {
|
||||
await writeFile(inputPath, imageBuffer);
|
||||
|
||||
// Process
|
||||
const onProgress = clientJobId
|
||||
const jobIdForProgress = clientJobId;
|
||||
const onProgress = jobIdForProgress
|
||||
? (percent: number, stage: string) => {
|
||||
updateSingleFileProgress({
|
||||
jobId: clientJobId!,
|
||||
jobId: jobIdForProgress,
|
||||
phase: "processing",
|
||||
stage,
|
||||
percent,
|
||||
|
||||
@@ -73,10 +73,11 @@ export function registerOcr(app: FastifyInstance) {
|
||||
const jobId = randomUUID();
|
||||
const workspacePath = await createWorkspace(jobId);
|
||||
|
||||
const onProgress = clientJobId
|
||||
const jobIdForProgress = clientJobId;
|
||||
const onProgress = jobIdForProgress
|
||||
? (percent: number, stage: string) => {
|
||||
updateSingleFileProgress({
|
||||
jobId: clientJobId!,
|
||||
jobId: jobIdForProgress,
|
||||
phase: "processing",
|
||||
stage,
|
||||
percent,
|
||||
|
||||
@@ -62,10 +62,11 @@ export function registerRemoveBackground(app: FastifyInstance) {
|
||||
await writeFile(inputPath, fileBuffer);
|
||||
|
||||
// Process
|
||||
const onProgress = clientJobId
|
||||
const jobIdForProgress = clientJobId;
|
||||
const onProgress = jobIdForProgress
|
||||
? (percent: number, stage: string) => {
|
||||
updateSingleFileProgress({
|
||||
jobId: clientJobId!,
|
||||
jobId: jobIdForProgress,
|
||||
phase: "processing",
|
||||
stage,
|
||||
percent,
|
||||
|
||||
@@ -75,9 +75,7 @@ function parseXmp(xmpBuffer: Buffer): Record<string, string> {
|
||||
const xml = xmpBuffer.toString("utf-8");
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
const attrRegex = /(\w+:\w+)="([^"]+)"/g;
|
||||
let match;
|
||||
while ((match = attrRegex.exec(xml)) !== null) {
|
||||
for (const match of xml.matchAll(/(\w+:\w+)="([^"]+)"/g)) {
|
||||
const key = match[1];
|
||||
if (key.startsWith("xmlns:") || key.startsWith("rdf:")) continue;
|
||||
result[key] = match[2];
|
||||
|
||||
@@ -62,10 +62,11 @@ export function registerUpscale(app: FastifyInstance) {
|
||||
await writeFile(inputPath, fileBuffer);
|
||||
|
||||
// Process
|
||||
const onProgress = clientJobId
|
||||
const jobIdForProgress = clientJobId;
|
||||
const onProgress = jobIdForProgress
|
||||
? (percent: number, stage: string) => {
|
||||
updateSingleFileProgress({
|
||||
jobId: clientJobId!,
|
||||
jobId: jobIdForProgress,
|
||||
phase: "processing",
|
||||
stage,
|
||||
percent,
|
||||
|
||||
Reference in New Issue
Block a user