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 { notificationPreferenceSchema } from "../schemas";
5import {
6 deleteWorkspaceRule,
7 getNotificationPreferences,
8 updateNotificationPreferences,
9 upsertWorkspaceRule,
10} from "./service";
11
12const httpErrorSchema = v.object({ message: v.string() });
13
14const workspaceRuleSchema = v.object({
15 isActive: v.boolean(),
16 emailEnabled: v.boolean(),
17 ntfyEnabled: v.boolean(),
18 gotifyEnabled: v.boolean(),
19 webhookEnabled: v.boolean(),
20 projectMode: v.picklist(["all", "selected"] as const),
21 selectedProjectIds: v.optional(v.array(v.string())),
22});
23
24const notificationPreferences = new Hono<{
25 Variables: {
26 userId: string;
27 userEmail: string;
28 };
29}>();
30
31notificationPreferences
32 .get(
33 "/",
34 describeRoute({
35 operationId: "getNotificationPreferences",
36 tags: ["Notification Preferences"],
37 description: "Get notification delivery preferences for the current user",
38 responses: {
39 200: {
40 description: "Notification preferences",
41 content: {
42 "application/json": {
43 schema: resolver(notificationPreferenceSchema),
44 },
45 },
46 },
47 400: {
48 description: "Validation error",
49 content: {
50 "application/json": { schema: resolver(httpErrorSchema) },
51 },
52 },
53 401: {
54 description: "Unauthorized",
55 content: {
56 "application/json": { schema: resolver(httpErrorSchema) },
57 },
58 },
59 403: {
60 description: "Forbidden",
61 content: {
62 "application/json": { schema: resolver(httpErrorSchema) },
63 },
64 },
65 },
66 }),
67 async (c) => {
68 const userId = c.get("userId");
69 const userEmail = c.get("userEmail");
70 return c.json(
71 await getNotificationPreferences(userId, userEmail || null),
72 );
73 },
74 )
75 .put(
76 "/",
77 describeRoute({
78 operationId: "updateNotificationPreferences",
79 tags: ["Notification Preferences"],
80 description: "Update global notification delivery preferences",
81 responses: {
82 200: {
83 description: "Updated notification preferences",
84 content: {
85 "application/json": {
86 schema: resolver(notificationPreferenceSchema),
87 },
88 },
89 },
90 400: {
91 description: "Validation error",
92 content: {
93 "application/json": { schema: resolver(httpErrorSchema) },
94 },
95 },
96 401: {
97 description: "Unauthorized",
98 content: {
99 "application/json": { schema: resolver(httpErrorSchema) },
100 },
101 },
102 403: {
103 description: "Forbidden",
104 content: {
105 "application/json": { schema: resolver(httpErrorSchema) },
106 },
107 },
108 },
109 }),
110 validator(
111 "json",
112 v.object({
113 emailEnabled: v.optional(v.boolean()),
114 ntfyEnabled: v.optional(v.boolean()),
115 ntfyServerUrl: v.optional(v.nullable(v.string())),
116 ntfyTopic: v.optional(v.nullable(v.string())),
117 ntfyToken: v.optional(v.nullable(v.string())),
118 gotifyEnabled: v.optional(v.boolean()),
119 gotifyServerUrl: v.optional(v.nullable(v.string())),
120 gotifyToken: v.optional(v.nullable(v.string())),
121 webhookEnabled: v.optional(v.boolean()),
122 webhookUrl: v.optional(v.nullable(v.string())),
123 webhookSecret: v.optional(v.nullable(v.string())),
124 }),
125 ),
126 async (c) => {
127 const userId = c.get("userId");
128 const userEmail = c.get("userEmail");
129 const body = c.req.valid("json");
130
131 return c.json(
132 await updateNotificationPreferences(userId, userEmail || null, body),
133 );
134 },
135 )
136 .put(
137 "/workspaces/:workspaceId",
138 describeRoute({
139 operationId: "upsertNotificationPreferenceWorkspaceRule",
140 tags: ["Notification Preferences"],
141 description: "Create or update a workspace notification rule",
142 responses: {
143 200: {
144 description: "Updated notification preferences",
145 content: {
146 "application/json": {
147 schema: resolver(notificationPreferenceSchema),
148 },
149 },
150 },
151 400: {
152 description: "Validation error",
153 content: {
154 "application/json": { schema: resolver(httpErrorSchema) },
155 },
156 },
157 401: {
158 description: "Unauthorized",
159 content: {
160 "application/json": { schema: resolver(httpErrorSchema) },
161 },
162 },
163 403: {
164 description: "Forbidden",
165 content: {
166 "application/json": { schema: resolver(httpErrorSchema) },
167 },
168 },
169 },
170 }),
171 validator("param", v.object({ workspaceId: v.string() })),
172 validator("json", workspaceRuleSchema),
173 async (c) => {
174 const userId = c.get("userId");
175 const userEmail = c.get("userEmail");
176 const { workspaceId } = c.req.valid("param");
177 const body = c.req.valid("json");
178
179 return c.json(
180 await upsertWorkspaceRule(userId, workspaceId, userEmail || null, body),
181 );
182 },
183 )
184 .delete(
185 "/workspaces/:workspaceId",
186 describeRoute({
187 operationId: "deleteNotificationPreferenceWorkspaceRule",
188 tags: ["Notification Preferences"],
189 description: "Delete a workspace notification rule",
190 responses: {
191 200: {
192 description: "Updated notification preferences",
193 content: {
194 "application/json": {
195 schema: resolver(notificationPreferenceSchema),
196 },
197 },
198 },
199 400: {
200 description: "Validation error",
201 content: {
202 "application/json": { schema: resolver(httpErrorSchema) },
203 },
204 },
205 401: {
206 description: "Unauthorized",
207 content: {
208 "application/json": { schema: resolver(httpErrorSchema) },
209 },
210 },
211 403: {
212 description: "Forbidden",
213 content: {
214 "application/json": { schema: resolver(httpErrorSchema) },
215 },
216 },
217 404: {
218 description: "Workspace rule not found",
219 content: {
220 "application/json": { schema: resolver(httpErrorSchema) },
221 },
222 },
223 },
224 }),
225 validator("param", v.object({ workspaceId: v.string() })),
226 async (c) => {
227 const userId = c.get("userId");
228 const userEmail = c.get("userEmail");
229 const { workspaceId } = c.req.valid("param");
230
231 return c.json(
232 await deleteWorkspaceRule(userId, workspaceId, userEmail || null),
233 );
234 },
235 );
236
237export default notificationPreferences;