kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { Hono } from "hono";
2import { describeRoute, resolver, validator } from "hono-openapi";
3import * as v from "valibot";
4import { activitySchema, projectSchema, taskSchema } from "../schemas";
5import { workspaceAccess } from "../utils/workspace-access-middleware";
6import globalSearch from "./controllers/global-search";
7
8const workspaceSchema = v.object({
9 id: v.string(),
10 name: v.string(),
11 slug: v.string(),
12 logo: v.nullable(v.string()),
13 metadata: v.nullable(v.string()),
14 description: v.nullable(v.string()),
15 createdAt: v.date(),
16});
17
18const searchResultSchema = v.object({
19 tasks: v.optional(v.array(taskSchema)),
20 projects: v.optional(v.array(projectSchema)),
21 workspaces: v.optional(v.array(workspaceSchema)),
22 comments: v.optional(v.array(activitySchema)),
23 activities: v.optional(v.array(activitySchema)),
24});
25
26const search = new Hono<{
27 Variables: {
28 userId: string;
29 };
30}>().get(
31 "/",
32 describeRoute({
33 operationId: "globalSearch",
34 tags: ["Search"],
35 description:
36 "Search across tasks, projects, workspaces, comments, and activities",
37 responses: {
38 200: {
39 description: "Search results",
40 content: {
41 "application/json": { schema: resolver(searchResultSchema) },
42 },
43 },
44 },
45 }),
46 validator(
47 "query",
48 v.object({
49 q: v.pipe(
50 v.string(),
51 v.minLength(1, "Query must be at least 1 character"),
52 ),
53 type: v.optional(
54 v.picklist([
55 "all",
56 "tasks",
57 "projects",
58 "workspaces",
59 "comments",
60 "activities",
61 ]),
62 "all",
63 ),
64 workspaceId: v.optional(v.string()),
65 projectId: v.optional(v.string()),
66 limit: v.optional(
67 v.pipe(
68 v.string(),
69 v.transform(Number),
70 v.minValue(1, "Limit must be at least 1"),
71 v.maxValue(50, "Limit must not exceed 50"),
72 ),
73 "20",
74 ),
75 userEmail: v.optional(v.pipe(v.string(), v.email())),
76 }),
77 ),
78 workspaceAccess.fromQuery(),
79 async (c) => {
80 const { q, type, workspaceId, projectId, limit, userEmail } =
81 c.req.valid("query");
82 const userId = c.get("userId");
83
84 const results = await globalSearch({
85 query: q,
86 userId,
87 userEmail,
88 type,
89 workspaceId,
90 projectId,
91 limit: typeof limit === "string" ? Number(limit) : limit,
92 });
93
94 return c.json(results);
95 },
96);
97
98export default search;