From 71d211efbdd475b22ae043116069339bf612ede7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Olguncu?= <21091016+ogzhanolguncu@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:22:26 +0300 Subject: [PATCH 1/4] docs: new contribution guideline for client structure (#2793) * docs: new contribution guideline for client structure * fix: styling issues and revert mint.json change * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../docs/contributing/client-structure.mdx | 151 ++++++++++++++++++ .../content/docs/contributing/meta.json | 2 +- 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 apps/engineering/content/docs/contributing/client-structure.mdx diff --git a/apps/engineering/content/docs/contributing/client-structure.mdx b/apps/engineering/content/docs/contributing/client-structure.mdx new file mode 100644 index 0000000000..e976f286bb --- /dev/null +++ b/apps/engineering/content/docs/contributing/client-structure.mdx @@ -0,0 +1,151 @@ +--- +title: Client-Side Structure +description: How to structure your PR when contributing to Unkey dashboard +--- + +# Client-Side Structure Contribution Guidelines + +## Overview + +When contributing to the Unkey dashboard or any client app in the Unkey repository, we follow a feature-based architecture. This guide will help you understand how to structure your code to maintain consistency across the project. + +## Directory Structure + +Each feature (typically corresponding to a Next.js page) should be organized as a self-contained module with the following structure: + +``` +feature-name/ +├── components/ # Feature-specific React components +│ ├── component-name/ # Complex components get their own directory +│ │ ├── index.tsx +│ │ └── sub-component.tsx +│ └── simple-component.tsx +├── hooks/ # Custom hooks for the feature +│ ├── queries/ # API query hooks +│ │ ├── use-feature-list.ts +│ │ └── use-feature-details.ts +│ └── use-feature-logic.ts +├── actions/ # Server actions and API calls +│ └── feature-actions.ts +├── types/ # TypeScript types and interfaces +│ └── feature.ts +├── schemas/ # Validation schemas +│ └── feature.ts +├── utils/ # Helper functions +│ └── feature-helpers.ts +├── constants.ts # Feature-specific constants +└── page.tsx # Main page component +``` + +## Key Principles + +1. **Feature Isolation** + + - Keep all related code within the feature directory + - Don't import feature-specific components into other features + - Use shared components from the global `/components` directory or `unkey/ui` package for common UI elements + +2. **Component Organization** + + - Simple components can be single files **(No need to break everything into 50 different files, follow your common sense)** + - Complex components should have their own directory with an index.tsx + - Keep component-specific styles, tests, and utilities close to the component + +3. **Code Colocation** + - Place related code as close as possible to where it's used + - If a utility is only used by one component, keep it in the component's directory + +## Example Page Structure + +Here's an example of how to structure a typical page component: + +```typescript +import { Navbar } from "@/components/navbar"; // Global shared component +import { PageContent } from "@/components/page-content"; +import { FeatureComponent } from "./components/feature-component"; + +export default function FeaturePage() { + // Page implementation + // This is also we where we do our server side data fetching. + return ( +
+ {/* Navigation content */} + + {/* Entry to our actual component. This one is usually a client-side component */} + + +
+ ); +} +``` + +## Best Practices + +1. **File Naming** + + - Use kebab-case for directory and file names + - The directory structure itself provides context, so explicit suffixes are optional + - If you choose to use suffixes for additional clarity, common patterns include: + - `auth.schema.ts` or just `auth-schema.ts` for validation schemas + - `auth.type.ts` or just `auth-types.ts` for type definitions + - `.client.tsx` for client-specific components + - `.server.ts` for server-only code + - `.action.ts` for server actions + +2. **Code Organization** + + - Keep files focused and single-purpose + - Use index.ts files to expose public API of complex components + - Colocate tests with the code they test + - Place shared types in the feature's `types` directory + +3. **Imports and Exports** + - Use absolute imports for shared components (`@/components`) + - Never use default exports unless it's absolutely necessary + - Use relative imports within a feature + - Export complex components through index files + - Avoid circular dependencies + +## Shared Code + +Global shared code should be placed in root-level directories: + +``` +/components # Shared React components +/hooks # Shared custom hooks +/utils # Shared utilities +/types # Shared TypeScript types +/constants # Global constants +``` + +Only place code in these directories if it's used across multiple features. + +## Example Feature Implementation + +Here's a practical example of how to structure a feature: + +```typescript +// /feature/components/feature-list/index.tsx +export function FeatureList() { + // Component implementation +} + +// /feature/hooks/queries/use-features.ts +export function useFeatures() { + // Hook implementation +} + +// /feature/actions/feature-actions.ts +export async function createFeature() { + // Server action implementation +} + +// /feature/types/feature.ts +export interface Feature { + // Type definitions +} +``` + +## Questions? + +If you're unsure about where to place certain code or how to structure your feature, please don't hesitate to ask in our Discord community or in your pull request. We're here to help! diff --git a/apps/engineering/content/docs/contributing/meta.json b/apps/engineering/content/docs/contributing/meta.json index 20bc193e01..2d533ed3db 100644 --- a/apps/engineering/content/docs/contributing/meta.json +++ b/apps/engineering/content/docs/contributing/meta.json @@ -3,5 +3,5 @@ "description": "oss/acc", "icon": "GitPullRequest", "root": true, - "pages": ["index", "sdk-development", "testing", "versions"] + "pages": ["index", "sdk-development", "testing", "versions", "client-structure"] } From 7664efc41d8b2a6156033cf3b4e02e70ef173cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Olguncu?= <21091016+ogzhanolguncu@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:45:48 +0300 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20correct=20disabled=20stats=20metric?= =?UTF-8?q?=20displaying=20valid=20count=20instead=20of=20=E2=80=A6=20(#27?= =?UTF-8?q?96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: correct disabled stats metric displaying valid count instead of disabled * fix: maintain the order of verfication requests --- .../[apiId]/keys/[keyAuthId]/[keyId]/page.tsx | 39 ++++++++--- .../dashboard/components/dashboard/charts.tsx | 65 +++++++++++++++++-- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx index f94ecb0bdd..72bfda7652 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx @@ -111,6 +111,9 @@ export default async function APIKeyDetailPage(props: { .then((res) => res.val?.at(0)?.time ?? 0), ]); + // Sort all verifications by time first + const sortedVerifications = verifications.val!.sort((a, b) => a.time - b.time); + const successOverTime: { x: string; y: number }[] = []; const ratelimitedOverTime: { x: string; y: number }[] = []; const usageExceededOverTime: { x: string; y: number }[] = []; @@ -119,30 +122,47 @@ export default async function APIKeyDetailPage(props: { const expiredOverTime: { x: string; y: number }[] = []; const forbiddenOverTime: { x: string; y: number }[] = []; - for (const d of verifications.val!.sort((a, b) => a.time - b.time)) { + // Get all unique timestamps + const uniqueDates = [...new Set(sortedVerifications.map((d) => d.time))].sort((a, b) => a - b); + + // Ensure each array has entries for all timestamps with zero counts + for (const timestamp of uniqueDates) { + const x = new Date(timestamp).toISOString(); + successOverTime.push({ x, y: 0 }); + ratelimitedOverTime.push({ x, y: 0 }); + usageExceededOverTime.push({ x, y: 0 }); + disabledOverTime.push({ x, y: 0 }); + insufficientPermissionsOverTime.push({ x, y: 0 }); + expiredOverTime.push({ x, y: 0 }); + forbiddenOverTime.push({ x, y: 0 }); + } + + for (const d of sortedVerifications) { const x = new Date(d.time).toISOString(); + const index = uniqueDates.indexOf(d.time); + switch (d.outcome) { case "": case "VALID": - successOverTime.push({ x, y: d.count }); + successOverTime[index] = { x, y: d.count }; break; case "RATE_LIMITED": - ratelimitedOverTime.push({ x, y: d.count }); + ratelimitedOverTime[index] = { x, y: d.count }; break; case "USAGE_EXCEEDED": - usageExceededOverTime.push({ x, y: d.count }); + usageExceededOverTime[index] = { x, y: d.count }; break; case "DISABLED": - disabledOverTime.push({ x, y: d.count }); + disabledOverTime[index] = { x, y: d.count }; break; case "INSUFFICIENT_PERMISSIONS": - insufficientPermissionsOverTime.push({ x, y: d.count }); + insufficientPermissionsOverTime[index] = { x, y: d.count }; break; case "EXPIRED": - expiredOverTime.push({ x, y: d.count }); + expiredOverTime[index] = { x, y: d.count }; break; case "FORBIDDEN": - forbiddenOverTime.push({ x, y: d.count }); + forbiddenOverTime[index] = { x, y: d.count }; break; } } @@ -209,6 +229,7 @@ export default async function APIKeyDetailPage(props: { stats.forbidden += v.count; } }); + const roleTee = key.workspace.roles.map((role) => { const nested: NestedPermissions = {}; for (const permission of key.workspace.permissions) { @@ -328,7 +349,7 @@ export default async function APIKeyDetailPage(props: { - + ) => { const { resolvedTheme } = useTheme(); - const colors: { light: Record; dark: Record } = { + const colors: { + light: Record; + dark: Record; + } = { light: { primary: "#1c1917", warn: "#FFCD07", @@ -133,9 +136,9 @@ export const LineChart: React.FC<{ tooltip={{ formatter: (datum) => ({ name: datum.category, - value: `${Intl.NumberFormat(undefined, { notation: "compact" }).format( - Number(datum.y), - )} ms`, + value: `${Intl.NumberFormat(undefined, { + notation: "compact", + }).format(Number(datum.y))} ms`, }), }} /> @@ -202,6 +205,53 @@ export const StackedColumnChart: React.FC<{ colors: Array; }> = ({ data, timeGranularity, colors }) => { const { axisColor } = useColors(colors); + + const formatDate = (date: string) => { + const d = new Date(date); + if (Number.isNaN(d.getTime())) { + return date; + } + + switch (timeGranularity) { + case "minute": + return d.toLocaleString(undefined, { + hour: "numeric", + minute: "2-digit", + hour12: true, + month: "short", + day: "numeric", + }); + case "hour": + return d.toLocaleString(undefined, { + hour: "numeric", + hour12: true, + month: "short", + day: "numeric", + year: "numeric", + }); + case "day": + return d.toLocaleString(undefined, { + weekday: "short", + month: "short", + day: "numeric", + year: "numeric", + }); + case "month": + return d.toLocaleString(undefined, { + month: "long", + year: "numeric", + }); + default: + return d.toLocaleString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "2-digit", + }); + } + }; + return ( ({ name: datum.category, - value: Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(datum.y)), + value: Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 1, + compactDisplay: "short", + }).format(Number(datum.y)), }), }} /> From 0746b3311aa40abf8093f138de595d6cc74d8e2e Mon Sep 17 00:00:00 2001 From: James P Date: Thu, 9 Jan 2025 08:21:12 -0500 Subject: [PATCH 3/4] fix: Add error types to SDK (#2797) * Fix type errors This returns all errors as a type from Unkey versus just one * Adding changeset * [autofix.ci] apply automated fixes --------- Co-authored-by: James Perkins Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/twelve-kiwis-live.md | 5 +++++ packages/api/src/errors.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/twelve-kiwis-live.md diff --git a/.changeset/twelve-kiwis-live.md b/.changeset/twelve-kiwis-live.md new file mode 100644 index 0000000000..e305bf0b18 --- /dev/null +++ b/.changeset/twelve-kiwis-live.md @@ -0,0 +1,5 @@ +--- +"@unkey/api": minor +--- + +Add the types for error codes diff --git a/packages/api/src/errors.ts b/packages/api/src/errors.ts index 866ff16421..f04cedbeb8 100644 --- a/packages/api/src/errors.ts +++ b/packages/api/src/errors.ts @@ -2,4 +2,10 @@ import type { paths } from "./openapi"; // this is what a json body response looks like export type ErrorResponse = - paths["/v1/liveness"]["get"]["responses"]["500"]["content"]["application/json"]; + | paths["/v1/liveness"]["get"]["responses"]["400"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["401"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["403"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["404"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["409"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["429"]["content"]["application/json"] + | paths["/v1/liveness"]["get"]["responses"]["500"]["content"]["application/json"]; From a4d0af64ee500b829ff1bac286bce73e22d49b0a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:34:09 -0500 Subject: [PATCH 4/4] chore(release): version packages (#2798) Co-authored-by: github-actions[bot] --- .changeset/twelve-kiwis-live.md | 5 ----- apps/dashboard/CHANGELOG.md | 6 ++++++ apps/dashboard/package.json | 2 +- packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 13 ++++++++++--- packages/hono/CHANGELOG.md | 7 +++++++ packages/hono/package.json | 13 ++++++++++--- packages/nextjs/CHANGELOG.md | 7 +++++++ packages/nextjs/package.json | 13 ++++++++++--- packages/ratelimit/CHANGELOG.md | 7 +++++++ packages/ratelimit/package.json | 14 +++++++++++--- 11 files changed, 75 insertions(+), 18 deletions(-) delete mode 100644 .changeset/twelve-kiwis-live.md diff --git a/.changeset/twelve-kiwis-live.md b/.changeset/twelve-kiwis-live.md deleted file mode 100644 index e305bf0b18..0000000000 --- a/.changeset/twelve-kiwis-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@unkey/api": minor ---- - -Add the types for error codes diff --git a/apps/dashboard/CHANGELOG.md b/apps/dashboard/CHANGELOG.md index 1eb8fec7b4..ab40f851a0 100644 --- a/apps/dashboard/CHANGELOG.md +++ b/apps/dashboard/CHANGELOG.md @@ -1,5 +1,11 @@ # @unkey/web +## 0.1.40 + +### Patch Changes + +- @unkey/ratelimit@0.5.1 + ## 0.1.39 ### Patch Changes diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 8796f2ecaf..f1dec61d7c 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@unkey/dashboard", - "version": "0.1.39", + "version": "0.1.40", "private": true, "scripts": { "dev": "next dev", diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index e0824693fe..4cec01e337 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @unkey/api +## 0.30.0 + +### Minor Changes + +- 0746b33: Add the types for error codes + ## 0.29.0 ### Minor Changes diff --git a/packages/api/package.json b/packages/api/package.json index 3584ed5c9b..3af102777e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@unkey/api", - "version": "0.29.0", + "version": "0.30.0", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", @@ -9,12 +9,19 @@ "publishConfig": { "access": "public" }, - "keywords": ["unkey", "client", "api"], + "keywords": [ + "unkey", + "client", + "api" + ], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": ["./dist/**", "README.md"], + "files": [ + "./dist/**", + "README.md" + ], "author": "Andreas Thomas ", "scripts": { "generate": "openapi-typescript https://api.unkey.dev/openapi.json -o ./src/openapi.d.ts", diff --git a/packages/hono/CHANGELOG.md b/packages/hono/CHANGELOG.md index 6f3b3d77dd..00da89b888 100644 --- a/packages/hono/CHANGELOG.md +++ b/packages/hono/CHANGELOG.md @@ -1,5 +1,12 @@ # @unkey/hono +## 1.4.11 + +### Patch Changes + +- Updated dependencies [0746b33] + - @unkey/api@0.30.0 + ## 1.4.10 ### Patch Changes diff --git a/packages/hono/package.json b/packages/hono/package.json index 82c7598c66..fcabc2fa00 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -1,6 +1,6 @@ { "name": "@unkey/hono", - "version": "1.4.10", + "version": "1.4.11", "main": "./dist/index.js", "types": "./dist/index.d.ts", "license": "MIT", @@ -8,12 +8,19 @@ "publishConfig": { "access": "public" }, - "keywords": ["unkey", "client", "api", "hono"], + "keywords": [ + "unkey", + "client", + "api", + "hono" + ], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": ["./dist/**"], + "files": [ + "./dist/**" + ], "author": "Andreas Thomas ", "scripts": { "build": "tsup", diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index 2c942a3bb6..490cd960a3 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -1,5 +1,12 @@ # @unkey/nextjs +## 0.18.4 + +### Patch Changes + +- Updated dependencies [0746b33] + - @unkey/api@0.30.0 + ## 0.18.3 ### Patch Changes diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index a8ada0e7ad..11bdf331b9 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@unkey/nextjs", - "version": "0.18.3", + "version": "0.18.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "license": "MIT", @@ -8,12 +8,19 @@ "publishConfig": { "access": "public" }, - "keywords": ["unkey", "client", "api"], + "keywords": [ + "unkey", + "client", + "api" + ], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": ["./dist/**", "README.md"], + "files": [ + "./dist/**", + "README.md" + ], "author": "Andreas Thomas ", "scripts": { "build": "tsup" diff --git a/packages/ratelimit/CHANGELOG.md b/packages/ratelimit/CHANGELOG.md index b51453d18b..e481e8bd81 100644 --- a/packages/ratelimit/CHANGELOG.md +++ b/packages/ratelimit/CHANGELOG.md @@ -1,5 +1,12 @@ # @unkey/ratelimit +## 0.5.1 + +### Patch Changes + +- Updated dependencies [0746b33] + - @unkey/api@0.30.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/ratelimit/package.json b/packages/ratelimit/package.json index 8e1639e4bd..fb5c5e1006 100644 --- a/packages/ratelimit/package.json +++ b/packages/ratelimit/package.json @@ -1,6 +1,6 @@ { "name": "@unkey/ratelimit", - "version": "0.5.0", + "version": "0.5.1", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", @@ -9,12 +9,20 @@ "publishConfig": { "access": "public" }, - "keywords": ["unkey", "ratelimit", "global", "serverless"], + "keywords": [ + "unkey", + "ratelimit", + "global", + "serverless" + ], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": ["./dist/**", "README.md"], + "files": [ + "./dist/**", + "README.md" + ], "author": "Andreas Thomas ", "scripts": { "build": "tsup"