a cache for slack profile pictures and emojis
1import { serve } from "bun";
2import { version } from "../package.json";
3
4// Define the Swagger specification
5const swaggerSpec = {
6 openapi: "3.0.0",
7 info: {
8 title: "Cachet",
9 version: version,
10 description:
11 "Hi 👋\n\nThis is a pretty simple API that acts as a middleman caching layer between slack and the outside world. There may be authentication in the future, but for now, it's just a simple cache.\n\nThe `/r` endpoints are redirects to the actual image URLs, so you can use them as direct image links.",
12 contact: {
13 name: "Kieran Klukas",
14 email: "me@dunkirk.sh",
15 },
16 license: {
17 name: "AGPL 3.0",
18 url: "https://github.com/taciturnaxoltol/cachet/blob/master/LICENSE.md",
19 },
20 },
21 tags: [
22 {
23 name: "The Cache!",
24 description: "*must be read in an ominous voice*",
25 },
26 {
27 name: "Status",
28 description: "*Rather boring status endpoints :(*",
29 },
30 ],
31 paths: {
32 "/users/{user}": {
33 get: {
34 tags: ["The Cache!"],
35 summary: "Get user information",
36 description:
37 "Retrieves user information from the cache or from Slack if not cached",
38 parameters: [
39 {
40 name: "user",
41 in: "path",
42 required: true,
43 schema: {
44 type: "string",
45 },
46 description: "Slack user ID",
47 },
48 ],
49 responses: {
50 "200": {
51 description: "User information",
52 content: {
53 "application/json": {
54 schema: {
55 type: "object",
56 properties: {
57 id: {
58 type: "string",
59 example: "90750e24-c2f0-4c52-8681-e6176da6e7ab",
60 },
61 expiration: {
62 type: "string",
63 format: "date-time",
64 example: new Date().toISOString(),
65 },
66 user: {
67 type: "string",
68 example: "U12345678",
69 },
70 displayName: {
71 type: "string",
72 example: "krn",
73 },
74 pronouns: {
75 type: "string",
76 nullable: true,
77 example: "possibly/blank",
78 },
79 image: {
80 type: "string",
81 example:
82 "https://avatars.slack-edge.com/2024-11-30/8105375749571_53898493372773a01a1f_original.jpg",
83 },
84 },
85 },
86 },
87 },
88 },
89 "404": {
90 description: "User not found",
91 content: {
92 "application/json": {
93 schema: {
94 type: "object",
95 properties: {
96 message: {
97 type: "string",
98 example: "User not found",
99 },
100 },
101 },
102 },
103 },
104 },
105 "500": {
106 description: "Error fetching user from Slack",
107 content: {
108 "application/json": {
109 schema: {
110 type: "object",
111 properties: {
112 message: {
113 type: "string",
114 example: "Error fetching user from Slack",
115 },
116 },
117 },
118 },
119 },
120 },
121 },
122 },
123 },
124 "/users/{user}/r": {
125 get: {
126 tags: ["The Cache!"],
127 summary: "Redirect to user profile image",
128 description: "Redirects to the user's profile image URL",
129 parameters: [
130 {
131 name: "user",
132 in: "path",
133 required: true,
134 schema: {
135 type: "string",
136 },
137 description: "Slack user ID",
138 },
139 ],
140 responses: {
141 "302": {
142 description: "Redirect to user profile image",
143 },
144 "307": {
145 description: "Redirect to default image when user not found",
146 },
147 "500": {
148 description: "Error fetching user from Slack",
149 content: {
150 "application/json": {
151 schema: {
152 type: "object",
153 properties: {
154 message: {
155 type: "string",
156 example: "Error fetching user from Slack",
157 },
158 },
159 },
160 },
161 },
162 },
163 },
164 },
165 },
166 "/users/{user}/purge": {
167 post: {
168 tags: ["The Cache!"],
169 summary: "Purge user cache",
170 description: "Purges a specific user's cache",
171 parameters: [
172 {
173 name: "user",
174 in: "path",
175 required: true,
176 schema: {
177 type: "string",
178 },
179 description: "Slack user ID",
180 },
181 {
182 name: "authorization",
183 in: "header",
184 required: true,
185 schema: {
186 type: "string",
187 example: "Bearer <token>",
188 },
189 description: "Bearer token for authentication",
190 },
191 ],
192 responses: {
193 "200": {
194 description: "User cache purged",
195 content: {
196 "application/json": {
197 schema: {
198 type: "object",
199 properties: {
200 message: {
201 type: "string",
202 example: "User cache purged",
203 },
204 userId: {
205 type: "string",
206 example: "U12345678",
207 },
208 success: {
209 type: "boolean",
210 example: true,
211 },
212 },
213 },
214 },
215 },
216 },
217 "401": {
218 description: "Unauthorized",
219 content: {
220 "text/plain": {
221 schema: {
222 type: "string",
223 example: "Unauthorized",
224 },
225 },
226 },
227 },
228 },
229 },
230 },
231 "/emojis": {
232 get: {
233 tags: ["The Cache!"],
234 summary: "Get all emojis",
235 description: "Retrieves all emojis from the cache",
236 responses: {
237 "200": {
238 description: "List of emojis",
239 content: {
240 "application/json": {
241 schema: {
242 type: "array",
243 items: {
244 type: "object",
245 properties: {
246 id: {
247 type: "string",
248 example: "5427fe70-686f-4684-9da5-95d9ef4c1090",
249 },
250 expiration: {
251 type: "string",
252 format: "date-time",
253 example: new Date().toISOString(),
254 },
255 name: {
256 type: "string",
257 example: "blahaj-heart",
258 },
259 alias: {
260 type: "string",
261 nullable: true,
262 example: "blobhaj-heart",
263 },
264 image: {
265 type: "string",
266 example:
267 "https://emoji.slack-edge.com/T0266FRGM/blahaj-heart/db9adf8229e9a4fb.png",
268 },
269 },
270 },
271 },
272 },
273 },
274 },
275 },
276 },
277 },
278 "/emojis/{emoji}": {
279 get: {
280 tags: ["The Cache!"],
281 summary: "Get emoji information",
282 description: "Retrieves information about a specific emoji",
283 parameters: [
284 {
285 name: "emoji",
286 in: "path",
287 required: true,
288 schema: {
289 type: "string",
290 },
291 description: "Emoji name",
292 },
293 ],
294 responses: {
295 "200": {
296 description: "Emoji information",
297 content: {
298 "application/json": {
299 schema: {
300 type: "object",
301 properties: {
302 id: {
303 type: "string",
304 example: "9ed0a560-928d-409c-89fc-10fe156299da",
305 },
306 expiration: {
307 type: "string",
308 format: "date-time",
309 example: new Date().toISOString(),
310 },
311 name: {
312 type: "string",
313 example: "orphmoji-yay",
314 },
315 image: {
316 type: "string",
317 example:
318 "https://emoji.slack-edge.com/T0266FRGM/orphmoji-yay/23a37f4af47092d3.png",
319 },
320 },
321 },
322 },
323 },
324 },
325 "404": {
326 description: "Emoji not found",
327 content: {
328 "application/json": {
329 schema: {
330 type: "object",
331 properties: {
332 message: {
333 type: "string",
334 example: "Emoji not found",
335 },
336 },
337 },
338 },
339 },
340 },
341 },
342 },
343 },
344 "/emojis/{emoji}/r": {
345 get: {
346 tags: ["The Cache!"],
347 summary: "Redirect to emoji image",
348 description: "Redirects to the emoji image URL",
349 parameters: [
350 {
351 name: "emoji",
352 in: "path",
353 required: true,
354 schema: {
355 type: "string",
356 },
357 description: "Emoji name",
358 },
359 ],
360 responses: {
361 "302": {
362 description: "Redirect to emoji image",
363 },
364 "404": {
365 description: "Emoji not found",
366 content: {
367 "application/json": {
368 schema: {
369 type: "object",
370 properties: {
371 message: {
372 type: "string",
373 example: "Emoji not found",
374 },
375 },
376 },
377 },
378 },
379 },
380 },
381 },
382 },
383 "/reset": {
384 post: {
385 tags: ["The Cache!"],
386 summary: "Reset cache",
387 description: "Purges all items from the cache",
388 parameters: [
389 {
390 name: "authorization",
391 in: "header",
392 required: true,
393 schema: {
394 type: "string",
395 example: "Bearer <token>",
396 },
397 description: "Bearer token for authentication",
398 },
399 ],
400 responses: {
401 "200": {
402 description: "Cache purged",
403 content: {
404 "application/json": {
405 schema: {
406 type: "object",
407 properties: {
408 message: {
409 type: "string",
410 example: "Cache purged",
411 },
412 users: {
413 type: "number",
414 example: 10,
415 },
416 emojis: {
417 type: "number",
418 example: 100,
419 },
420 },
421 },
422 },
423 },
424 },
425 "401": {
426 description: "Unauthorized",
427 content: {
428 "text/plain": {
429 schema: {
430 type: "string",
431 example: "Unauthorized",
432 },
433 },
434 },
435 },
436 },
437 },
438 },
439 "/health": {
440 get: {
441 tags: ["Status"],
442 summary: "Health check",
443 description:
444 "Checks the health of the API, Slack connection, and database",
445 responses: {
446 "200": {
447 description: "Health check passed",
448 content: {
449 "application/json": {
450 schema: {
451 type: "object",
452 properties: {
453 http: {
454 type: "boolean",
455 example: true,
456 },
457 slack: {
458 type: "boolean",
459 example: true,
460 },
461 database: {
462 type: "boolean",
463 example: true,
464 },
465 },
466 },
467 },
468 },
469 },
470 "500": {
471 description: "Health check failed",
472 content: {
473 "application/json": {
474 schema: {
475 type: "object",
476 properties: {
477 http: {
478 type: "boolean",
479 example: false,
480 },
481 slack: {
482 type: "boolean",
483 example: false,
484 },
485 database: {
486 type: "boolean",
487 example: false,
488 },
489 },
490 },
491 },
492 },
493 },
494 },
495 },
496 },
497 "/stats": {
498 get: {
499 tags: ["Status"],
500 summary: "Get analytics statistics",
501 description: "Retrieves analytics statistics for the API",
502 parameters: [
503 {
504 name: "days",
505 in: "query",
506 required: false,
507 schema: {
508 type: "string",
509 },
510 description: "Number of days to look back (default: 7)",
511 },
512 ],
513 responses: {
514 "200": {
515 description: "Analytics statistics",
516 content: {
517 "application/json": {
518 schema: {
519 type: "object",
520 properties: {
521 totalRequests: {
522 type: "number",
523 },
524 requestsByEndpoint: {
525 type: "array",
526 items: {
527 type: "object",
528 properties: {
529 endpoint: {
530 type: "string",
531 },
532 count: {
533 type: "number",
534 },
535 averageResponseTime: {
536 type: "number",
537 },
538 },
539 },
540 },
541 // Additional properties omitted for brevity
542 },
543 },
544 },
545 },
546 },
547 },
548 },
549 },
550 },
551};
552
553// Export the Swagger specification for use in other files
554export default swaggerSpec;