172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import { z } from "zod";
|
|
import { Fastify } from "../types";
|
|
import { kvGet } from "@/app/kv/kvGet";
|
|
import { kvList } from "@/app/kv/kvList";
|
|
import { kvBulkGet } from "@/app/kv/kvBulkGet";
|
|
import { kvMutate } from "@/app/kv/kvMutate";
|
|
import { log } from "@/utils/log";
|
|
|
|
export function kvRoutes(app: Fastify) {
|
|
// GET /v1/kv/:key - Get single value
|
|
app.get('/v1/kv/:key', {
|
|
preHandler: app.authenticate,
|
|
schema: {
|
|
params: z.object({
|
|
key: z.string()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
version: z.number()
|
|
}).nullable(),
|
|
404: z.object({
|
|
error: z.literal('Key not found')
|
|
}),
|
|
500: z.object({
|
|
error: z.literal('Failed to get value')
|
|
})
|
|
}
|
|
}
|
|
}, async (request, reply) => {
|
|
const userId = request.userId;
|
|
const { key } = request.params;
|
|
|
|
try {
|
|
const result = await kvGet({ uid: userId }, key);
|
|
|
|
if (!result) {
|
|
return reply.code(404).send({ error: 'Key not found' });
|
|
}
|
|
|
|
return reply.send(result);
|
|
} catch (error) {
|
|
log({ module: 'api', level: 'error' }, `Failed to get KV value: ${error}`);
|
|
return reply.code(500).send({ error: 'Failed to get value' });
|
|
}
|
|
});
|
|
|
|
// GET /v1/kv - List key-value pairs with optional prefix filter
|
|
app.get('/v1/kv', {
|
|
preHandler: app.authenticate,
|
|
schema: {
|
|
querystring: z.object({
|
|
prefix: z.string().optional(),
|
|
limit: z.coerce.number().int().min(1).max(1000).default(100)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
items: z.array(z.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
version: z.number()
|
|
}))
|
|
}),
|
|
500: z.object({
|
|
error: z.literal('Failed to list items')
|
|
})
|
|
}
|
|
}
|
|
}, async (request, reply) => {
|
|
const userId = request.userId;
|
|
const { prefix, limit } = request.query;
|
|
|
|
try {
|
|
const result = await kvList({ uid: userId }, { prefix, limit });
|
|
return reply.send(result);
|
|
} catch (error) {
|
|
log({ module: 'api', level: 'error' }, `Failed to list KV items: ${error}`);
|
|
return reply.code(500).send({ error: 'Failed to list items' });
|
|
}
|
|
});
|
|
|
|
// POST /v1/kv/bulk - Bulk get values
|
|
app.post('/v1/kv/bulk', {
|
|
preHandler: app.authenticate,
|
|
schema: {
|
|
body: z.object({
|
|
keys: z.array(z.string()).min(1).max(100)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
values: z.array(z.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
version: z.number()
|
|
}))
|
|
}),
|
|
500: z.object({
|
|
error: z.literal('Failed to get values')
|
|
})
|
|
}
|
|
}
|
|
}, async (request, reply) => {
|
|
const userId = request.userId;
|
|
const { keys } = request.body;
|
|
|
|
try {
|
|
const result = await kvBulkGet({ uid: userId }, keys);
|
|
return reply.send(result);
|
|
} catch (error) {
|
|
log({ module: 'api', level: 'error' }, `Failed to bulk get KV values: ${error}`);
|
|
return reply.code(500).send({ error: 'Failed to get values' });
|
|
}
|
|
});
|
|
|
|
// PUT /v1/kv - Atomic batch mutation
|
|
app.post('/v1/kv', {
|
|
preHandler: app.authenticate,
|
|
schema: {
|
|
body: z.object({
|
|
mutations: z.array(z.object({
|
|
key: z.string(),
|
|
value: z.string().nullable(),
|
|
version: z.number() // Always required, use -1 for new keys
|
|
})).min(1).max(100)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
success: z.literal(true),
|
|
results: z.array(z.object({
|
|
key: z.string(),
|
|
version: z.number()
|
|
}))
|
|
}),
|
|
409: z.object({
|
|
success: z.literal(false),
|
|
errors: z.array(z.object({
|
|
key: z.string(),
|
|
error: z.literal('version-mismatch'),
|
|
version: z.number(),
|
|
value: z.string().nullable()
|
|
}))
|
|
}),
|
|
500: z.object({
|
|
error: z.literal('Failed to mutate values')
|
|
})
|
|
}
|
|
}
|
|
}, async (request, reply) => {
|
|
const userId = request.userId;
|
|
const { mutations } = request.body;
|
|
|
|
try {
|
|
const result = await kvMutate({ uid: userId }, mutations);
|
|
|
|
if (!result.success) {
|
|
return reply.code(409).send({
|
|
success: false as const,
|
|
errors: result.errors!
|
|
});
|
|
}
|
|
|
|
return reply.send({
|
|
success: true as const,
|
|
results: result.results!
|
|
});
|
|
} catch (error) {
|
|
log({ module: 'api', level: 'error' }, `Failed to mutate KV values: ${error}`);
|
|
return reply.code(500).send({ error: 'Failed to mutate values' });
|
|
}
|
|
});
|
|
} |