Openstatus www.openstatus.dev

chore: migrate unkey v2 (#1638)

* chore: migrate unkey v2

* fix: tsc

* fix: tsc and tests

* chore: improve error message

* fix: ternary operation

authored by

Maximilian Kaske and committed by
GitHub
ab4574d0 5003141b

+93 -64
+1
apps/dashboard/package.json
··· 73 73 "@trpc/react-query": "11.4.4", 74 74 "@trpc/server": "11.4.4", 75 75 "@trpc/tanstack-react-query": "11.4.4", 76 + "@unkey/api": "2.2.0", 76 77 "class-variance-authority": "0.7.1", 77 78 "clsx": "2.1.1", 78 79 "cmdk": "1.1.1",
+1 -1
apps/dashboard/src/components/data-table/incidents/data-table-row-actions.tsx
··· 109 109 <QuickActions 110 110 actions={actions} 111 111 deleteAction={{ 112 - confirmationValue: row.original.title ?? "incident", 112 + confirmationValue: row.original.title || "incident", 113 113 submitAction: async () => { 114 114 await deleteIncidentMutation.mutateAsync({ 115 115 id: row.original.id,
+3 -3
apps/dashboard/src/components/forms/settings/form-api-key.tsx
··· 51 51 const createApiKeyMutation = useMutation( 52 52 trpc.apiKey.create.mutationOptions({ 53 53 onSuccess: (data) => { 54 - if (data.result) { 55 - setResult(data.result); 54 + if (data) { 55 + setResult(data); 56 56 } else { 57 57 throw new Error("Failed to create API key"); 58 58 } ··· 131 131 confirmationValue="API Key" 132 132 submitAction={async () => { 133 133 await revokeApiKeyMutation.mutateAsync({ 134 - keyId: apiKey.id, 134 + keyId: apiKey.keyId, 135 135 }); 136 136 }} 137 137 />
+1 -1
apps/server/package.json
··· 29 29 "@openstatus/utils": "workspace:*", 30 30 "@scalar/hono-api-reference": "0.8.5", 31 31 "@t3-oss/env-core": "0.7.1", 32 - "@unkey/api": "0.26.1", 32 + "@unkey/api": "2.2.0", 33 33 "@upstash/qstash": "2.6.2", 34 34 "hono": "4.5.3", 35 35 "nanoid": "5.0.7",
+1 -1
apps/server/src/libs/cache/memory.ts
··· 41 41 42 42 clear() { 43 43 this.data.clear(); 44 - for (const timer of this.timers.values()) { 44 + for (const timer of Array.from(this.timers.values())) { 45 45 clearTimeout(timer); 46 46 } 47 47 this.timers.clear();
+4
apps/server/src/libs/checker/utils.ts
··· 33 33 headers: transformHeaders(monitor.otelHeaders), 34 34 } 35 35 : undefined, 36 + retry: monitor.retry ?? 0, 37 + followRedirects: monitor.followRedirects ?? false, 36 38 }; 37 39 case "tcp": 38 40 return { ··· 51 53 headers: transformHeaders(monitor.otelHeaders), 52 54 } 53 55 : undefined, 56 + retry: monitor.retry ?? 0, 57 + followRedirects: monitor.followRedirects ?? false, 54 58 }; 55 59 default: 56 60 throw new OpenStatusApiError({
+29 -7
apps/server/src/libs/middlewares/auth.ts
··· 1 - import { verifyKey } from "@unkey/api"; 1 + import { UnkeyCore } from "@unkey/api/core"; 2 + import { keysVerifyKey } from "@unkey/api/funcs/keysVerifyKey"; 2 3 import type { Context, Next } from "hono"; 3 4 4 5 import { env } from "@/env"; ··· 22 23 23 24 if (error) { 24 25 throw new OpenStatusApiError({ 25 - code: "INTERNAL_SERVER_ERROR", 26 + code: "UNAUTHORIZED", 26 27 message: error.message, 27 28 }); 28 29 } 29 - if (!result?.valid || !result?.ownerId) { 30 + 31 + if (!result.valid || !result.ownerId) { 30 32 throw new OpenStatusApiError({ 31 33 code: "UNAUTHORIZED", 32 34 message: "Invalid API Key", 33 35 }); 34 36 } 35 37 38 + const ownerId = Number.parseInt(result.ownerId); 39 + 40 + if (Number.isNaN(ownerId)) { 41 + throw new OpenStatusApiError({ 42 + code: "UNAUTHORIZED", 43 + message: "API Key is Not a Number", 44 + }); 45 + } 46 + 36 47 const _workspace = await db 37 48 .select() 38 49 .from(workspace) 39 - .where(eq(workspace.id, Number.parseInt(result.ownerId))) 50 + .where(eq(workspace.id, ownerId)) 40 51 .get(); 41 52 42 53 if (!_workspace) { ··· 72 83 * > We cannot use `os_` as a prefix for our own keys. 73 84 */ 74 85 if (key.startsWith("os_")) { 75 - const { result, error } = await verifyKey(key); 86 + const unkey = new UnkeyCore({ rootKey: env.UNKEY_TOKEN }); 87 + const res = await keysVerifyKey(unkey, { key }); 88 + if (!res.ok) { 89 + console.error("Unkey Error", res.error?.message); 90 + return { 91 + result: { valid: false, ownerId: undefined }, 92 + error: { message: "Invalid API verification" }, 93 + }; 94 + } 76 95 return { 77 - result: { valid: result?.valid ?? false, ownerId: result?.ownerId }, 78 - error: error ? { message: error.message } : undefined, 96 + result: { 97 + valid: res.value.data.valid, 98 + ownerId: res.value.data.identity?.externalId, 99 + }, 100 + error: undefined, 79 101 }; 80 102 } 81 103 // Special bypass for our workspace
+1 -2
apps/server/src/routes/v1/check/http/post.test.ts
··· 3 3 import { afterEach, mock } from "bun:test"; 4 4 import { app } from "@/index"; 5 5 6 - // @ts-expect-error - FIXME: requires a function... 7 6 const mockFetch = mock(); 8 7 9 - global.fetch = mockFetch; 8 + global.fetch = mockFetch as unknown as typeof fetch; 10 9 mock.module("node-fetch", () => mockFetch); 11 10 12 11 afterEach(() => {
+1 -3
apps/server/src/routes/v1/index.ts
··· 108 108 api.get( 109 109 "/", 110 110 Scalar({ 111 - spec: { 112 - url: "/v1/openapi", 113 - }, 111 + url: "/v1/openapi", 114 112 servers: [ 115 113 { 116 114 url: "https://api.openstatus.dev/v1",
+2
apps/server/tsconfig.json
··· 3 3 "include": ["src", "*.ts", "**/*.ts"], 4 4 "compilerOptions": { 5 5 "jsx": "react-jsx", 6 + "module": "preserve", 7 + "moduleResolution": "bundler", 6 8 "jsxImportSource": "react", 7 9 "allowJs": true, 8 10 "types": ["bun-types"],
-1
apps/web/package.json
··· 53 53 "@trpc/next": "11.4.4", 54 54 "@trpc/react-query": "11.4.4", 55 55 "@trpc/server": "11.4.4", 56 - "@unkey/api": "0.26.1", 57 56 "@upstash/qstash": "2.6.2", 58 57 "@upstash/redis": "1.22.1", 59 58 "@vercel/blob": "0.23.3",
+1
apps/web/tsconfig.json
··· 1 1 { 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "compilerOptions": { 4 + "moduleResolution": "bundler", 4 5 "baseUrl": ".", 5 6 "paths": { 6 7 "@/*": ["./src/*"]
+1 -1
packages/api/package.json
··· 21 21 "@t3-oss/env-core": "0.7.1", 22 22 "@trpc/client": "11.4.4", 23 23 "@trpc/server": "11.4.4", 24 - "@unkey/api": "0.26.1", 24 + "@unkey/api": "2.2.0", 25 25 "@vercel/blob": "0.23.3", 26 26 "date-fns": "2.30.0", 27 27 "nanoid": "5.0.7",
+32 -18
packages/api/src/router/apiKey.ts
··· 1 - import { Unkey } from "@unkey/api"; 1 + import { UnkeyCore } from "@unkey/api/core"; 2 + import { apisListKeys } from "@unkey/api/funcs/apisListKeys"; 3 + import { keysCreateKey } from "@unkey/api/funcs/keysCreateKey"; 4 + import { keysDeleteKey } from "@unkey/api/funcs/keysDeleteKey"; 2 5 import { z } from "zod"; 3 6 4 7 import { Events } from "@openstatus/analytics"; ··· 14 17 .meta({ track: Events.CreateAPI }) 15 18 .input(z.object({ ownerId: z.number() })) 16 19 .mutation(async ({ input, ctx }) => { 17 - const unkey = new Unkey({ token: env.UNKEY_TOKEN, cache: "no-cache" }); 20 + const unkey = new UnkeyCore({ rootKey: env.UNKEY_TOKEN }); 18 21 19 22 const allowedWorkspaces = await db 20 23 .select() ··· 33 36 }); 34 37 } 35 38 36 - const key = await unkey.keys.create({ 39 + const res = await keysCreateKey(unkey, { 37 40 apiId: env.UNKEY_API_ID, 38 - ownerId: String(input.ownerId), 41 + externalId: String(input.ownerId), 39 42 prefix: "os", 40 43 }); 41 44 42 - console.log(key); 45 + if (!res.ok) { 46 + throw new TRPCError({ 47 + code: "BAD_REQUEST", 48 + message: res.error.message, 49 + }); 50 + } 43 51 44 - return key; 52 + return res.value.data; 45 53 }), 46 54 47 55 revoke: protectedProcedure 48 56 .meta({ track: Events.RevokeAPI }) 49 57 .input(z.object({ keyId: z.string() })) 50 58 .mutation(async ({ input }) => { 51 - const unkey = new Unkey({ token: env.UNKEY_TOKEN, cache: "no-cache" }); 59 + const unkey = new UnkeyCore({ rootKey: env.UNKEY_TOKEN }); 52 60 53 - const res = await unkey.keys.delete({ keyId: input.keyId }); 54 - return res; 61 + const res = await keysDeleteKey(unkey, { keyId: input.keyId }); 62 + 63 + if (!res.ok) { 64 + throw new TRPCError({ 65 + code: "BAD_REQUEST", 66 + message: res.error.message, 67 + }); 68 + } 69 + 70 + return res.value; 55 71 }), 56 72 57 73 get: protectedProcedure.query(async ({ ctx }) => { 58 - const unkey = new Unkey({ token: env.UNKEY_TOKEN, cache: "no-cache" }); 74 + const unkey = new UnkeyCore({ rootKey: env.UNKEY_TOKEN }); 59 75 60 - const data = await unkey.apis.listKeys({ 76 + const res = await apisListKeys(unkey, { 77 + externalId: String(ctx.workspace.id), 61 78 apiId: env.UNKEY_API_ID, 62 - ownerId: String(ctx.workspace.id), 63 79 }); 64 80 65 - if (data?.error) { 81 + if (!res.ok) { 66 82 throw new TRPCError({ 67 - code: "INTERNAL_SERVER_ERROR", 68 - message: "Something went wrong. Please contact us.", 83 + code: "BAD_REQUEST", 84 + message: res.error.message, 69 85 }); 70 86 } 71 87 72 - const value = data.result.keys?.[0] || null; 73 - 74 - return value; 88 + return res.value.data[0]; 75 89 }), 76 90 });
+1
packages/api/tsconfig.json
··· 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "include": ["src", "*.ts"], 4 4 "compilerOptions": { 5 + "moduleResolution": "bundler", 5 6 "types": ["bun-types"] 6 7 } 7 8 }
+3
packages/db/src/schema/plan/config.ts
··· 27 27 INR: 0, 28 28 }, 29 29 limits: { 30 + version: undefined, 30 31 monitors: 1, 31 32 "synthetic-checks": 30, 32 33 periodicity: ["10m", "30m", "1h"], ··· 65 66 INR: 3000, 66 67 }, 67 68 limits: { 69 + version: undefined, 68 70 monitors: 20, 69 71 "synthetic-checks": 100, 70 72 periodicity: ["1m", "5m", "10m", "30m", "1h"], ··· 103 105 INR: 10000, 104 106 }, 105 107 limits: { 108 + version: undefined, 106 109 monitors: 50, 107 110 "synthetic-checks": 300, 108 111 periodicity: ["30s", "1m", "5m", "10m", "30m", "1h"],
+1 -1
packages/tinybird/src/client.ts
··· 588 588 .number() 589 589 .default(0) 590 590 .transform((val) => val !== 0), 591 - region: z.enum(monitorRegions).or(z.string()), 591 + region: z.enum(monitorRegions), 592 592 timestamp: z.number().int().optional(), 593 593 message: z.string().nullable().optional(), 594 594 timing: timingSchema,
+10 -25
pnpm-lock.yaml
··· 218 218 '@trpc/tanstack-react-query': 219 219 specifier: 11.4.4 220 220 version: 11.4.4(@tanstack/react-query@5.81.5(react@19.2.2))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.2.2(react@19.2.2))(react@19.2.2)(typescript@5.7.2) 221 + '@unkey/api': 222 + specifier: 2.2.0 223 + version: 2.2.0 221 224 class-variance-authority: 222 225 specifier: 0.7.1 223 226 version: 0.7.1 ··· 477 480 specifier: 0.7.1 478 481 version: 0.7.1(typescript@5.7.2)(zod@3.25.76) 479 482 '@unkey/api': 480 - specifier: 0.26.1 481 - version: 0.26.1 483 + specifier: 2.2.0 484 + version: 2.2.0 482 485 '@upstash/qstash': 483 486 specifier: 2.6.2 484 487 version: 2.6.2 ··· 895 898 '@trpc/server': 896 899 specifier: 11.4.4 897 900 version: 11.4.4(typescript@5.7.2) 898 - '@unkey/api': 899 - specifier: 0.26.1 900 - version: 0.26.1 901 901 '@upstash/qstash': 902 902 specifier: 2.6.2 903 903 version: 2.6.2 ··· 1196 1196 specifier: 11.4.4 1197 1197 version: 11.4.4(typescript@5.7.2) 1198 1198 '@unkey/api': 1199 - specifier: 0.26.1 1200 - version: 0.26.1 1199 + specifier: 2.2.0 1200 + version: 2.2.0 1201 1201 '@vercel/blob': 1202 1202 specifier: 0.23.3 1203 1203 version: 0.23.3 ··· 6822 6822 '@unhead/schema@1.11.20': 6823 6823 resolution: {integrity: sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==} 6824 6824 6825 - '@unkey/api@0.26.1': 6826 - resolution: {integrity: sha512-WSYOeZjGbFkO9yA4UsNS6NjprUF9OAYIlBHN/7iRUaFBq+PL502Qe10xNwiwoppYhW6PnrCFobW92vrkjHTcjA==} 6827 - 6828 - '@unkey/error@0.2.0': 6829 - resolution: {integrity: sha512-DFGb4A7SrusZPP0FYuRIF0CO+Gi4etLUAEJ6EKc+TKYmscL0nEJ2Pr38FyX9MvjI4Wx5l35Wc9KsBjMm9Ybh7w==} 6830 - 6831 - '@unkey/rbac@0.3.1': 6832 - resolution: {integrity: sha512-Hj+52XRIlBBl3/qOUq9K71Fwy3PWExBQOpOClVYHdrcmbgqNL6L4EdW/BzliLhqPCdwZTPVSJTnZ3Hw4ZYixsQ==} 6825 + '@unkey/api@2.2.0': 6826 + resolution: {integrity: sha512-XoFWPbZnyos04u+qQTAT+lHymRTNidMK64QywZaWNKbJmorPDd/jZlyKZvL+LNxxBlFXcHOHFtPHPk90wWmcvg==} 6833 6827 6834 6828 '@upstash/core-analytics@0.0.6': 6835 6829 resolution: {integrity: sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==} ··· 17581 17575 hookable: 5.5.3 17582 17576 zhead: 2.2.4 17583 17577 17584 - '@unkey/api@0.26.1': 17585 - dependencies: 17586 - '@unkey/rbac': 0.3.1 17587 - 17588 - '@unkey/error@0.2.0': 17578 + '@unkey/api@2.2.0': 17589 17579 dependencies: 17590 - zod: 3.25.76 17591 - 17592 - '@unkey/rbac@0.3.1': 17593 - dependencies: 17594 - '@unkey/error': 0.2.0 17595 17580 zod: 3.25.76 17596 17581 17597 17582 '@upstash/core-analytics@0.0.6':